sdforge 0.2.0__tar.gz → 0.2.1__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/sdforge.egg-info → sdforge-0.2.1}/PKG-INFO +4 -1
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/camera.py +2 -2
- sdforge-0.2.1/examples/constraints.py +213 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/debug.py +23 -7
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/export.py +1 -1
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/group.py +2 -2
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/light.py +3 -3
- sdforge-0.2.1/examples/material.py +65 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/noise.py +2 -2
- sdforge-0.2.1/examples/operations.py +149 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/params.py +3 -3
- sdforge-0.2.1/examples/primitives.py +130 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/render.py +1 -1
- sdforge-0.2.1/examples/saving.py +166 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/shaping.py +34 -2
- sdforge-0.2.1/examples/sketch.py +99 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/transforms.py +51 -35
- sdforge-0.2.1/sdforge/__init__.py +14 -0
- sdforge-0.2.1/sdforge/api/camera.py +29 -0
- sdforge-0.2.1/sdforge/api/constraints.py +277 -0
- sdforge-0.2.1/sdforge/api/core.py +618 -0
- sdforge-0.2.1/sdforge/api/cpu.py +748 -0
- sdforge-0.2.1/sdforge/api/debug.py +28 -0
- {sdforge-0.2.0/sdforge → sdforge-0.2.1/sdforge/api}/export.py +1 -1
- sdforge-0.2.1/sdforge/api/forge.py +45 -0
- sdforge-0.2.1/sdforge/api/gpu.py +163 -0
- sdforge-0.2.1/sdforge/api/group.py +51 -0
- sdforge-0.2.1/sdforge/api/light.py +32 -0
- sdforge-0.2.1/sdforge/api/loader.py +69 -0
- sdforge-0.2.1/sdforge/api/material.py +62 -0
- sdforge-0.2.1/sdforge/api/mesh.py +403 -0
- sdforge-0.2.1/sdforge/api/noise.py +35 -0
- sdforge-0.2.1/sdforge/api/operations.py +167 -0
- sdforge-0.2.1/sdforge/api/params.py +76 -0
- sdforge-0.2.1/sdforge/api/primitives.py +544 -0
- sdforge-0.2.1/sdforge/api/render.py +441 -0
- sdforge-0.2.1/sdforge/api/shaping.py +95 -0
- sdforge-0.2.1/sdforge/api/sketch.py +121 -0
- sdforge-0.2.1/sdforge/api/transforms.py +209 -0
- sdforge-0.2.1/sdforge/api/utils.py +55 -0
- sdforge-0.2.1/sdforge/glsl/debug.glsl +34 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/noise.glsl +8 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/operations.glsl +33 -1
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/primitives.glsl +71 -42
- sdforge-0.2.1/sdforge/glsl/raymarching.glsl +39 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/shaping.glsl +1 -1
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/transforms.glsl +18 -23
- {sdforge-0.2.0 → sdforge-0.2.1/sdforge.egg-info}/PKG-INFO +4 -1
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/SOURCES.txt +27 -7
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/requires.txt +4 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/setup.py +6 -2
- sdforge-0.2.1/tests/__init__.py +0 -0
- sdforge-0.2.1/tests/conftest.py +81 -0
- sdforge-0.2.1/tests/constraints.py +267 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/debug.py +54 -8
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/export.py +2 -2
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/forge.py +18 -26
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/group.py +3 -3
- sdforge-0.2.1/tests/material.py +77 -0
- sdforge-0.2.1/tests/noise.py +64 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/operations.py +95 -16
- sdforge-0.2.1/tests/primitives.py +241 -0
- sdforge-0.2.1/tests/render.py +172 -0
- sdforge-0.2.1/tests/saving.py +187 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/shaping.py +40 -13
- sdforge-0.2.1/tests/sketch.py +112 -0
- sdforge-0.2.1/tests/transforms.py +378 -0
- sdforge-0.2.0/examples/material.py +0 -23
- sdforge-0.2.0/examples/operations.py +0 -71
- sdforge-0.2.0/examples/primitives.py +0 -68
- sdforge-0.2.0/examples/saving.py +0 -79
- sdforge-0.2.0/sdforge/__init__.py +0 -30
- sdforge-0.2.0/sdforge/core.py +0 -340
- sdforge-0.2.0/sdforge/debug.py +0 -13
- sdforge-0.2.0/sdforge/glsl/debug.glsl +0 -15
- sdforge-0.2.0/sdforge/glsl/raymarching.glsl +0 -23
- sdforge-0.2.0/sdforge/loader.py +0 -32
- sdforge-0.2.0/sdforge/mesh.py +0 -150
- sdforge-0.2.0/sdforge/render.py +0 -309
- sdforge-0.2.0/sdforge/utils.py +0 -8
- sdforge-0.2.0/tests/conftest.py +0 -147
- sdforge-0.2.0/tests/material.py +0 -48
- sdforge-0.2.0/tests/noise.py +0 -50
- sdforge-0.2.0/tests/primitives.py +0 -144
- sdforge-0.2.0/tests/render.py +0 -95
- sdforge-0.2.0/tests/saving.py +0 -78
- sdforge-0.2.0/tests/transforms.py +0 -124
- {sdforge-0.2.0 → sdforge-0.2.1}/LICENSE +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/MANIFEST.in +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/README.md +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/__init__.py +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/examples/forge.py +0 -0
- {sdforge-0.2.0/tests → sdforge-0.2.1/sdforge/api}/__init__.py +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/camera.glsl +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/light.glsl +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/dependency_links.txt +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/top_level.txt +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/setup.cfg +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/camera.py +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/light.py +0 -0
- {sdforge-0.2.0 → sdforge-0.2.1}/tests/params.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A Python library for SDF modeling with real-time GLSL rendering and mesh export.
|
|
5
5
|
Home-page: https://github.com/nassimberrada/sdforge
|
|
6
6
|
Author: nassimberrada
|
|
@@ -23,9 +23,12 @@ Provides-Extra: ui
|
|
|
23
23
|
Requires-Dist: imgui[glfw]>=2.0.0; extra == "ui"
|
|
24
24
|
Provides-Extra: export
|
|
25
25
|
Requires-Dist: pygltflib; extra == "export"
|
|
26
|
+
Provides-Extra: saving
|
|
27
|
+
Requires-Dist: trimesh; extra == "saving"
|
|
26
28
|
Provides-Extra: full
|
|
27
29
|
Requires-Dist: imgui[glfw]>=2.0.0; extra == "full"
|
|
28
30
|
Requires-Dist: pygltflib; extra == "full"
|
|
31
|
+
Requires-Dist: trimesh; extra == "full"
|
|
29
32
|
Dynamic: author
|
|
30
33
|
Dynamic: classifier
|
|
31
34
|
Dynamic: description
|
|
@@ -6,7 +6,7 @@ def static_camera_example():
|
|
|
6
6
|
Returns a scene and a fixed Camera object.
|
|
7
7
|
The renderer will use this camera's position and target.
|
|
8
8
|
"""
|
|
9
|
-
scene = box(1.5
|
|
9
|
+
scene = box(1.5).round(0.1) | sphere(radius=1.2)
|
|
10
10
|
|
|
11
11
|
# Define a camera positioned at (4, 3, 4), looking at the origin.
|
|
12
12
|
cam = Camera(position=(4, 3, 4), target=(0, 0, 0), zoom=1.5)
|
|
@@ -19,7 +19,7 @@ def interactive_camera_example():
|
|
|
19
19
|
When no camera is provided to the render function, it defaults
|
|
20
20
|
to an interactive orbit camera controlled by the mouse.
|
|
21
21
|
"""
|
|
22
|
-
scene = box(1.5
|
|
22
|
+
scene = box(1.5).round(0.1) | sphere(radius=1.2)
|
|
23
23
|
return scene
|
|
24
24
|
|
|
25
25
|
def main():
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
from sdforge import box, sphere, cylinder, line, Param, Group, X, Y, Z, cone
|
|
4
|
+
from sdforge.api.constraints import (
|
|
5
|
+
tangent_offset,
|
|
6
|
+
midpoint,
|
|
7
|
+
distribute,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
def dimensional_equality_example():
|
|
11
|
+
"""
|
|
12
|
+
Demonstrates dimensional equality by using the same Param object
|
|
13
|
+
for multiple dimensions, ensuring they always match.
|
|
14
|
+
"""
|
|
15
|
+
p_hole_dia = Param("Hole Diameter", 0.8, 0.2, 1.5)
|
|
16
|
+
block = box(size=(3, 1, 2))
|
|
17
|
+
hole = cylinder(radius=p_hole_dia / 2, height=1.1)
|
|
18
|
+
sphere_on_top = sphere(radius=p_hole_dia / 2).translate((0, 1.0, 0))
|
|
19
|
+
|
|
20
|
+
scene = (block - hole) | sphere_on_top
|
|
21
|
+
return scene
|
|
22
|
+
|
|
23
|
+
def tangent_and_coincident_example():
|
|
24
|
+
"""
|
|
25
|
+
Demonstrates creating tangent and coincident relationships through
|
|
26
|
+
explicit calculation and construction.
|
|
27
|
+
"""
|
|
28
|
+
center_point = np.array([0.0, 0.0, 0.0])
|
|
29
|
+
c = cylinder(radius=1.0, height=0.2)
|
|
30
|
+
|
|
31
|
+
line_dir = np.array([1.0, 1.0, 0.0])
|
|
32
|
+
line_dir /= np.linalg.norm(line_dir)
|
|
33
|
+
|
|
34
|
+
offset = tangent_offset(circle_radius=1.0, line_direction=line_dir)
|
|
35
|
+
|
|
36
|
+
tangent_line = line(
|
|
37
|
+
start = center_point + offset - line_dir * 2,
|
|
38
|
+
end = center_point + offset + line_dir * 2,
|
|
39
|
+
radius=0.1
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
scene = c | tangent_line
|
|
43
|
+
return scene
|
|
44
|
+
|
|
45
|
+
def perpendicular_and_parallel_example():
|
|
46
|
+
"""
|
|
47
|
+
Demonstrates creating perpendicular and parallel features by construction,
|
|
48
|
+
using the .align_to() method.
|
|
49
|
+
"""
|
|
50
|
+
main_block = box(size=(2.0, 1.0, 1.5))
|
|
51
|
+
main_block_half_size = np.array(main_block.size) / 2.0
|
|
52
|
+
|
|
53
|
+
# A boss to be placed on the +X face, perpendicular to it.
|
|
54
|
+
boss = cylinder(radius=0.3, height=0.5)
|
|
55
|
+
|
|
56
|
+
# Define the point on the center of the +X face of the block
|
|
57
|
+
face_point_positive_x = X * main_block_half_size[0]
|
|
58
|
+
|
|
59
|
+
# Use the fluent helper method .align_to()
|
|
60
|
+
aligned_boss = boss.align_to(reference_point=face_point_positive_x, face_normal=X)
|
|
61
|
+
|
|
62
|
+
# A hole through the -X face.
|
|
63
|
+
hole = cylinder(radius=0.2, height=2.1)
|
|
64
|
+
face_point_negative_x = -X * main_block_half_size[0]
|
|
65
|
+
aligned_hole = hole.align_to(reference_point=face_point_negative_x, face_normal=-X)
|
|
66
|
+
|
|
67
|
+
scene = (main_block | aligned_boss) - aligned_hole
|
|
68
|
+
return scene
|
|
69
|
+
|
|
70
|
+
def angle_and_midpoint_example():
|
|
71
|
+
"""
|
|
72
|
+
Demonstrates placing features at an angle and at the midpoint of an edge.
|
|
73
|
+
"""
|
|
74
|
+
flange = cylinder(radius=2.0, height=0.5) - cylinder(radius=1.5, height=0.6)
|
|
75
|
+
hole = cylinder(radius=0.2, height=0.6)
|
|
76
|
+
|
|
77
|
+
# Use the fluent .place_at_angle() method
|
|
78
|
+
num_holes = 6
|
|
79
|
+
holes = []
|
|
80
|
+
for i in range(num_holes):
|
|
81
|
+
angle = i * (2 * np.pi / num_holes)
|
|
82
|
+
h = hole.place_at_angle(
|
|
83
|
+
pivot_point=(0,0,0),
|
|
84
|
+
axis=Y,
|
|
85
|
+
angle_rad=angle,
|
|
86
|
+
distance=1.75
|
|
87
|
+
)
|
|
88
|
+
holes.append(h)
|
|
89
|
+
|
|
90
|
+
corner_a = np.array([2.5, 0, -0.5])
|
|
91
|
+
corner_b = np.array([2.5, 0, 0.5])
|
|
92
|
+
slot_center = midpoint(corner_a, corner_b)
|
|
93
|
+
slot = box(size=(0.2, 0.6, 1.5)).round(0.1).translate(slot_center)
|
|
94
|
+
|
|
95
|
+
scene = (flange - Group(*holes)) | slot
|
|
96
|
+
return scene
|
|
97
|
+
|
|
98
|
+
def offset_and_bounds_example():
|
|
99
|
+
"""
|
|
100
|
+
Demonstrates offsetting an object along a vector and creating a
|
|
101
|
+
bounding box around a complex shape.
|
|
102
|
+
"""
|
|
103
|
+
base_part = box(1.0).scale((2, 0.5, 1)) | sphere(radius=0.8).translate(X * 0.7)
|
|
104
|
+
|
|
105
|
+
# Use fluent .offset_along()
|
|
106
|
+
pillar_start_point = np.array([0.0, -1.0, 0.0])
|
|
107
|
+
pillar_direction = np.array([-1.0, -0.5, 1.0])
|
|
108
|
+
pillar = cylinder(0.2, 1.5)
|
|
109
|
+
placed_pillar = pillar.offset_along(pillar_start_point, pillar_direction, 2.0)
|
|
110
|
+
|
|
111
|
+
# Generate a bounding box using the fluent .bounding_box() method
|
|
112
|
+
bbox = base_part.bounding_box(padding=0.1)
|
|
113
|
+
|
|
114
|
+
# Make the bounding box hollow
|
|
115
|
+
enclosure = bbox.shell(0.05)
|
|
116
|
+
|
|
117
|
+
final_scene = enclosure.color(0.4, 0.4, 0.5) | base_part.color(1, 0, 0) | placed_pillar
|
|
118
|
+
return final_scene
|
|
119
|
+
|
|
120
|
+
def stacking_example():
|
|
121
|
+
"""
|
|
122
|
+
Demonstrates the .stack() method to place objects relative to each other
|
|
123
|
+
without manual coordinate calculations.
|
|
124
|
+
"""
|
|
125
|
+
# 1. Start with a large base
|
|
126
|
+
base = box(size=(3.0, 0.5, 3.0)).round(0.1)
|
|
127
|
+
|
|
128
|
+
# 2. Stack a cylinder on top (+Y)
|
|
129
|
+
# The system calculates the top face of the box and bottom face of the cylinder
|
|
130
|
+
# and positions them to touch.
|
|
131
|
+
pedestal = cylinder(radius=1.0, height=1.0)
|
|
132
|
+
stage_1 = base.stack(pedestal, direction=(0, 1, 0))
|
|
133
|
+
|
|
134
|
+
# 3. Stack a sphere on top of the resulting union, with a small gap
|
|
135
|
+
orb = sphere(radius=0.8).color(1, 0.2, 0.2)
|
|
136
|
+
final_scene = stage_1.stack(orb, direction=(0, 1, 0), spacing=0.2)
|
|
137
|
+
|
|
138
|
+
return final_scene
|
|
139
|
+
|
|
140
|
+
def distribute_example():
|
|
141
|
+
"""
|
|
142
|
+
Demonstrates the distribute() function to layout multiple objects sequentially.
|
|
143
|
+
"""
|
|
144
|
+
shapes = [
|
|
145
|
+
box(1.0).color(0.8, 0.2, 0.2), # Red Box
|
|
146
|
+
sphere(0.6).color(0.2, 0.8, 0.2), # Green Sphere
|
|
147
|
+
cylinder(0.4, 1.5).color(0.2, 0.2, 0.8), # Blue Cylinder
|
|
148
|
+
cone(1.0, 0.5).color(0.8, 0.8, 0.2) # Yellow Cone
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
# Distribute them along the X axis with a 0.5 unit gap between each
|
|
152
|
+
scene = distribute(shapes, direction=(1, 0, 0), spacing=0.5)
|
|
153
|
+
|
|
154
|
+
return scene
|
|
155
|
+
|
|
156
|
+
def design_patterns_example():
|
|
157
|
+
"""
|
|
158
|
+
Demonstrates achieving constraints like concentricity and symmetry
|
|
159
|
+
using core SDForge features as a design pattern.
|
|
160
|
+
"""
|
|
161
|
+
center = np.array([2.0, 0.5, 0.0])
|
|
162
|
+
|
|
163
|
+
outer_cyl = cylinder(radius=0.8, height=1.0).translate(center)
|
|
164
|
+
inner_cyl = cylinder(radius=0.5, height=1.2).translate(center)
|
|
165
|
+
concentric_part = outer_cyl - inner_cyl
|
|
166
|
+
|
|
167
|
+
quarter_shape = box(size=(1, 0.4, 1)).round(0.1).translate((0.5, 0.2, 0.5))
|
|
168
|
+
cutout = sphere(radius=0.3).translate((0.8, 0.2, 0.8))
|
|
169
|
+
quarter_part = quarter_shape - cutout
|
|
170
|
+
|
|
171
|
+
symmetrical_part = quarter_part.mirror(X | Z).translate((-2.0, 0, 0))
|
|
172
|
+
|
|
173
|
+
scene = concentric_part | symmetrical_part
|
|
174
|
+
return scene
|
|
175
|
+
|
|
176
|
+
def main():
|
|
177
|
+
"""
|
|
178
|
+
Renders a constraints example based on a command-line argument.
|
|
179
|
+
"""
|
|
180
|
+
print("--- SDForge Constraints by Construction Examples ---")
|
|
181
|
+
|
|
182
|
+
examples = {
|
|
183
|
+
"dims_equal": dimensional_equality_example,
|
|
184
|
+
"tangent": tangent_and_coincident_example,
|
|
185
|
+
"alignment": perpendicular_and_parallel_example,
|
|
186
|
+
"angle_midpoint": angle_and_midpoint_example,
|
|
187
|
+
"offset_bounds": offset_and_bounds_example,
|
|
188
|
+
"stack": stacking_example,
|
|
189
|
+
"distribute": distribute_example,
|
|
190
|
+
"patterns": design_patterns_example,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if len(sys.argv) < 2:
|
|
194
|
+
print("\nPlease provide the name of an example to run.")
|
|
195
|
+
print("Available examples:")
|
|
196
|
+
for key in examples:
|
|
197
|
+
print(f" - {key}")
|
|
198
|
+
print(f"\nUsage: python {sys.argv[0]} <example_name>")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
example_name = sys.argv[1]
|
|
202
|
+
scene_func = examples.get(example_name)
|
|
203
|
+
|
|
204
|
+
if not scene_func:
|
|
205
|
+
print(f"\nError: Example '{example_name}' not found.")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
209
|
+
scene = scene_func()
|
|
210
|
+
scene.render()
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
main()
|
|
@@ -3,25 +3,41 @@ from sdforge import box, sphere, Debug
|
|
|
3
3
|
|
|
4
4
|
def normals_debug_example():
|
|
5
5
|
"""Visualizes the surface normals as colors."""
|
|
6
|
-
scene = box(1.5
|
|
6
|
+
scene = box(1.5).round(0.1) - sphere(radius=1.2)
|
|
7
7
|
debug = Debug('normals')
|
|
8
8
|
return scene, debug
|
|
9
9
|
|
|
10
10
|
def steps_debug_example():
|
|
11
11
|
"""Visualizes the number of raymarching steps."""
|
|
12
|
-
scene = box(1.5
|
|
12
|
+
scene = box(1.5).round(0.1) - sphere(radius=1.2)
|
|
13
13
|
debug = Debug('steps')
|
|
14
14
|
return scene, debug
|
|
15
15
|
|
|
16
|
+
def slice_debug_example():
|
|
17
|
+
"""
|
|
18
|
+
Visualizes a 2D cross-section of the distance field.
|
|
19
|
+
|
|
20
|
+
Orange = Positive distance (Outside)
|
|
21
|
+
Blue = Negative distance (Inside)
|
|
22
|
+
White = Surface boundary
|
|
23
|
+
"""
|
|
24
|
+
# A hollow sphere cut in half
|
|
25
|
+
scene = sphere(1.0).shell(0.1) - box(1.0).translate((0.5, 0, 0))
|
|
26
|
+
|
|
27
|
+
# Slice through the XY plane at Z=0, with a view scale of 3.0 units
|
|
28
|
+
debug = Debug('slice', plane='xy', slice_height=0.0, view_scale=3.0)
|
|
29
|
+
return scene, debug
|
|
30
|
+
|
|
16
31
|
def main():
|
|
17
32
|
"""Renders a debug example based on a command-line argument."""
|
|
18
33
|
print("--- SDForge Debug Examples ---")
|
|
19
|
-
|
|
34
|
+
|
|
20
35
|
examples = {
|
|
21
36
|
"normals": normals_debug_example,
|
|
22
37
|
"steps": steps_debug_example,
|
|
38
|
+
"slice": slice_debug_example,
|
|
23
39
|
}
|
|
24
|
-
|
|
40
|
+
|
|
25
41
|
if len(sys.argv) < 2:
|
|
26
42
|
print("\nPlease provide the name of an example to run.")
|
|
27
43
|
print("Available examples:")
|
|
@@ -32,17 +48,17 @@ def main():
|
|
|
32
48
|
|
|
33
49
|
example_name = sys.argv[1]
|
|
34
50
|
scene_func = examples.get(example_name)
|
|
35
|
-
|
|
51
|
+
|
|
36
52
|
if not scene_func:
|
|
37
53
|
print(f"\nError: Example '{example_name}' not found.")
|
|
38
54
|
return
|
|
39
55
|
|
|
40
56
|
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
41
57
|
result = scene_func()
|
|
42
|
-
|
|
58
|
+
|
|
43
59
|
scene, debug = None, None
|
|
44
60
|
for item in result:
|
|
45
|
-
from sdforge.core import SDFNode
|
|
61
|
+
from sdforge.api.core import SDFNode
|
|
46
62
|
if isinstance(item, SDFNode): scene = item
|
|
47
63
|
if isinstance(item, Debug): debug = item
|
|
48
64
|
|
|
@@ -10,7 +10,7 @@ def main():
|
|
|
10
10
|
|
|
11
11
|
# Create a scene with some complexity and a parameter.
|
|
12
12
|
p_radius = Param("Sphere Radius", 0.8, 0.5, 1.5)
|
|
13
|
-
scene = box(1.5
|
|
13
|
+
scene = box(1.5).round(0.1) - sphere(radius=p_radius)
|
|
14
14
|
|
|
15
15
|
output_path = "exported_shader.glsl"
|
|
16
16
|
print(f"Exporting scene to '{output_path}'...")
|
|
@@ -7,8 +7,8 @@ def group_transform_example():
|
|
|
7
7
|
Demonstrates applying a single transformation to a group of objects.
|
|
8
8
|
"""
|
|
9
9
|
# Create two separate objects, offset from the origin.
|
|
10
|
-
b = box(size=(1.5, 0.5, 0.5)
|
|
11
|
-
s = sphere(0.5).translate((1, 0, 0))
|
|
10
|
+
b = box(size=(1.5, 0.5, 0.5)).round(0.1).translate((-1, 0, 0))
|
|
11
|
+
s = sphere(radius=0.5).translate((1, 0, 0))
|
|
12
12
|
|
|
13
13
|
# Combine them into a group.
|
|
14
14
|
g = Group(b, s)
|
|
@@ -4,13 +4,13 @@ from sdforge import box, sphere, Light, Camera
|
|
|
4
4
|
|
|
5
5
|
def static_light_example():
|
|
6
6
|
"""A scene lit by a single, fixed-position light."""
|
|
7
|
-
scene = box(1.5
|
|
7
|
+
scene = box(1.5).round(0.1) | sphere(radius=1.2)
|
|
8
8
|
light = Light(position=(4, 5, 3), shadow_softness=16.0)
|
|
9
9
|
return scene, light
|
|
10
10
|
|
|
11
11
|
def headlight_example():
|
|
12
12
|
"""A scene lit by a headlight attached to the camera."""
|
|
13
|
-
scene = box(1.5
|
|
13
|
+
scene = box(1.5).round(0.1) | sphere(radius=1.2)
|
|
14
14
|
# When Light() is created with no position, it defaults to the camera's position.
|
|
15
15
|
light = Light(shadow_softness=32.0, ao_strength=5.0)
|
|
16
16
|
return scene, light
|
|
@@ -46,7 +46,7 @@ def main():
|
|
|
46
46
|
|
|
47
47
|
# Unpack the result tuple which may contain scene, light, and/or camera
|
|
48
48
|
scene, light, cam = None, None, None
|
|
49
|
-
from sdforge.core import SDFNode
|
|
49
|
+
from sdforge.api.core import SDFNode
|
|
50
50
|
for item in result:
|
|
51
51
|
if isinstance(item, SDFNode): scene = item
|
|
52
52
|
if isinstance(item, Light): light = item
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from sdforge import box, sphere, cylinder, X
|
|
3
|
+
|
|
4
|
+
def simple_material_example():
|
|
5
|
+
"""
|
|
6
|
+
Demonstrates applying different colors to objects.
|
|
7
|
+
"""
|
|
8
|
+
# A blue sphere is subtracted from a red box.
|
|
9
|
+
red_box = box(1.5).round(0.1).color(1.0, 0.2, 0.2)
|
|
10
|
+
blue_sphere = sphere(radius=1.2).color(0.3, 0.5, 1.0)
|
|
11
|
+
|
|
12
|
+
scene = red_box - blue_sphere
|
|
13
|
+
return scene
|
|
14
|
+
|
|
15
|
+
def masked_material_example():
|
|
16
|
+
"""
|
|
17
|
+
Demonstrates applying a color to a specific region using a mask.
|
|
18
|
+
This creates a 'decal' effect or a pattern.
|
|
19
|
+
"""
|
|
20
|
+
# Base object: White box
|
|
21
|
+
base = box(2.0).color(0.9, 0.9, 0.9)
|
|
22
|
+
|
|
23
|
+
# Mask: A cylinder running through the box along X
|
|
24
|
+
# We want to paint the box red where this cylinder intersects it.
|
|
25
|
+
mask_shape = cylinder(radius=0.5, height=3.0).rotate(X, 1.57).translate((0, 0.5, 0))
|
|
26
|
+
|
|
27
|
+
# Apply red color using the mask.
|
|
28
|
+
# Where mask < 0 (inside cylinder), color is Red.
|
|
29
|
+
# Elsewhere, it keeps the previous color (White).
|
|
30
|
+
scene = base.color(1.0, 0.1, 0.1, mask=mask_shape)
|
|
31
|
+
|
|
32
|
+
return scene
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
"""
|
|
36
|
+
Renders an example based on a command-line argument.
|
|
37
|
+
"""
|
|
38
|
+
print("--- SDForge Material Examples ---")
|
|
39
|
+
|
|
40
|
+
examples = {
|
|
41
|
+
"simple": simple_material_example,
|
|
42
|
+
"masked": masked_material_example,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if len(sys.argv) < 2:
|
|
46
|
+
print("\nPlease provide the name of an example to run.")
|
|
47
|
+
print("Available examples:")
|
|
48
|
+
for key in examples:
|
|
49
|
+
print(f" - {key}")
|
|
50
|
+
print(f"\nUsage: python {sys.argv[0]} <example_name>")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
example_name = sys.argv[1]
|
|
54
|
+
scene_func = examples.get(example_name)
|
|
55
|
+
|
|
56
|
+
if not scene_func:
|
|
57
|
+
print(f"\nError: Example '{example_name}' not found.")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
61
|
+
scene = scene_func()
|
|
62
|
+
scene.render()
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -3,13 +3,13 @@ from sdforge import sphere, box
|
|
|
3
3
|
|
|
4
4
|
def noise_displacement_example():
|
|
5
5
|
"""Applies procedural noise to the surface of a sphere."""
|
|
6
|
-
s = sphere(
|
|
6
|
+
s = sphere(radius=1.2)
|
|
7
7
|
# Higher scale = finer detail, higher strength = more displacement.
|
|
8
8
|
return s.displace_by_noise(scale=8.0, strength=0.1)
|
|
9
9
|
|
|
10
10
|
def sine_wave_displacement_example():
|
|
11
11
|
"""Applies a custom GLSL sine wave displacement to a box."""
|
|
12
|
-
b = box(size=1.8
|
|
12
|
+
b = box(size=1.8).round(0.1)
|
|
13
13
|
# You can use any GLSL expression that returns a float.
|
|
14
14
|
# The variable 'p' represents the point in space being sampled.
|
|
15
15
|
glsl_code = "sin(p.x * 20.0) * sin(p.z * 20.0) * 0.05"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import numpy as np
|
|
3
|
+
from sdforge import sphere, box, cylinder, X, Y, Param
|
|
4
|
+
|
|
5
|
+
def union_example():
|
|
6
|
+
"""A sphere and a box joined together."""
|
|
7
|
+
s = sphere(radius=0.8)
|
|
8
|
+
b = box(size=(1.5, 0.5, 0.5))
|
|
9
|
+
return s | b
|
|
10
|
+
|
|
11
|
+
def intersection_example():
|
|
12
|
+
"""A lens shape created by intersecting two spheres."""
|
|
13
|
+
s1 = sphere(radius=1.0).translate((-0.5, 0, 0))
|
|
14
|
+
s2 = sphere(radius=1.0).translate((0.5, 0, 0))
|
|
15
|
+
return s1 & s2
|
|
16
|
+
|
|
17
|
+
def difference_example():
|
|
18
|
+
"""A box with a sphere carved out of it."""
|
|
19
|
+
b = box(size=1.5)
|
|
20
|
+
s = sphere(radius=1.0)
|
|
21
|
+
return b - s
|
|
22
|
+
|
|
23
|
+
def smooth_union_example():
|
|
24
|
+
"""Two spheres smoothly blended together."""
|
|
25
|
+
s1 = sphere(radius=0.7).translate((-0.5, 0, 0))
|
|
26
|
+
s2 = sphere(radius=0.7).translate((0.5, 0, 0))
|
|
27
|
+
# The 'blend' parameter controls the smoothness of the blend.
|
|
28
|
+
return s1.union(s2, blend=0.3)
|
|
29
|
+
|
|
30
|
+
def masked_blend_example():
|
|
31
|
+
"""
|
|
32
|
+
Demonstrates using a mask to apply smoothing only to specific areas.
|
|
33
|
+
The union transitions from a sharp crease to a smooth fillet.
|
|
34
|
+
"""
|
|
35
|
+
# Two spheres sitting next to each other
|
|
36
|
+
s1 = sphere(radius=0.7).translate((-0.6, 0, 0))
|
|
37
|
+
s2 = sphere(radius=0.7).translate((0.6, 0, 0))
|
|
38
|
+
|
|
39
|
+
# Mask: A box covering the top half (Y > 0)
|
|
40
|
+
# The blend will be 0.4 inside this box, and 0.0 (sharp) outside.
|
|
41
|
+
mask_obj = box(size=(3, 2, 3)).translate((0, 1.0, 0))
|
|
42
|
+
|
|
43
|
+
# Apply variable smoothing
|
|
44
|
+
return s1.union(s2, blend=0.4, mask=mask_obj, mask_falloff=0.2)
|
|
45
|
+
|
|
46
|
+
def fillet_difference_example():
|
|
47
|
+
"""Demonstrates subtracting a shape with a rounded (filleted) edge."""
|
|
48
|
+
plate = box(size=(2.0, 0.5, 2.0))
|
|
49
|
+
hole = cylinder(radius=0.5, height=1.0)
|
|
50
|
+
|
|
51
|
+
# blend=0.1 creates a rounded edge where the cylinder is subtracted.
|
|
52
|
+
scene = plate.difference(hole, blend=0.1)
|
|
53
|
+
return scene
|
|
54
|
+
|
|
55
|
+
def linear_difference_example():
|
|
56
|
+
"""Demonstrates subtracting a shape with a linear (chamfered) edge."""
|
|
57
|
+
plate = box(size=(2.0, 0.5, 2.0))
|
|
58
|
+
hole = cylinder(radius=0.5, height=1.0)
|
|
59
|
+
|
|
60
|
+
# blend=0.1, blend_type='linear' creates a 45-degree bevel.
|
|
61
|
+
scene = plate.difference(hole, blend=0.1, blend_type='linear')
|
|
62
|
+
return scene
|
|
63
|
+
|
|
64
|
+
def fillet_union_example():
|
|
65
|
+
"""Demonstrates joining two shapes with a filleted seam."""
|
|
66
|
+
s1 = sphere(0.8).translate(-X * 0.5)
|
|
67
|
+
s2 = sphere(0.8).translate(X * 0.5)
|
|
68
|
+
|
|
69
|
+
# The blend parameter smoothly blends the two spheres together.
|
|
70
|
+
scene = s1.union(s2, blend=0.4)
|
|
71
|
+
return scene
|
|
72
|
+
|
|
73
|
+
def morphing_example():
|
|
74
|
+
"""
|
|
75
|
+
Demonstrates interpolating between two shapes (morphing).
|
|
76
|
+
Uses a Param to make it interactive.
|
|
77
|
+
"""
|
|
78
|
+
s = sphere(radius=1.0)
|
|
79
|
+
b = box(size=1.5)
|
|
80
|
+
|
|
81
|
+
# Use a parameter to control the morph factor (0.0 = sphere, 1.0 = box)
|
|
82
|
+
p_morph = Param("Morph Factor", 0.5, 0.0, 1.0)
|
|
83
|
+
|
|
84
|
+
return s.morph(b, factor=p_morph)
|
|
85
|
+
|
|
86
|
+
def masked_morph_example():
|
|
87
|
+
"""
|
|
88
|
+
Demonstrates a partial morph. The object is a sphere on the left
|
|
89
|
+
and transitions into a box on the right.
|
|
90
|
+
"""
|
|
91
|
+
s = sphere(1.0)
|
|
92
|
+
b = box(1.5)
|
|
93
|
+
|
|
94
|
+
# Mask covers the right side (X > 0).
|
|
95
|
+
# Inside mask -> Factor 1.0 (Box)
|
|
96
|
+
# Outside mask -> Factor 0.0 (Sphere)
|
|
97
|
+
mask_obj = box(3.0).translate((1.5, 0, 0))
|
|
98
|
+
|
|
99
|
+
return s.morph(b, factor=1.0, mask=mask_obj, mask_falloff=0.5)
|
|
100
|
+
|
|
101
|
+
def main():
|
|
102
|
+
"""
|
|
103
|
+
Renders an example based on a command-line argument.
|
|
104
|
+
"""
|
|
105
|
+
print("--- SDForge Operation Examples ---")
|
|
106
|
+
|
|
107
|
+
examples = {
|
|
108
|
+
"union": union_example,
|
|
109
|
+
"intersection": intersection_example,
|
|
110
|
+
"difference": difference_example,
|
|
111
|
+
"smooth_union": smooth_union_example,
|
|
112
|
+
"masked_blend": masked_blend_example,
|
|
113
|
+
"fillet_cut": fillet_difference_example,
|
|
114
|
+
"linear_cut": linear_difference_example,
|
|
115
|
+
"fillet_join": fillet_union_example,
|
|
116
|
+
"morph": morphing_example,
|
|
117
|
+
"masked_morph": masked_morph_example,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if len(sys.argv) < 2:
|
|
121
|
+
print("\nPlease provide the name of an example to run.")
|
|
122
|
+
print("Available examples:")
|
|
123
|
+
for key in examples:
|
|
124
|
+
print(f" - {key}")
|
|
125
|
+
print(f"\nUsage: python {sys.argv[0]} <example_name>")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
example_name = sys.argv[1]
|
|
129
|
+
scene_func = examples.get(example_name)
|
|
130
|
+
|
|
131
|
+
if not scene_func:
|
|
132
|
+
print(f"\nError: Example '{example_name}' not found.")
|
|
133
|
+
print("Available examples are:")
|
|
134
|
+
for key in examples:
|
|
135
|
+
print(f" - {key}")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
139
|
+
result = scene_func()
|
|
140
|
+
if isinstance(result, tuple):
|
|
141
|
+
scene, cam = result
|
|
142
|
+
scene.render(camera=cam)
|
|
143
|
+
else:
|
|
144
|
+
scene = result
|
|
145
|
+
scene.render()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
main()
|
|
@@ -5,7 +5,7 @@ import numpy as np
|
|
|
5
5
|
def main():
|
|
6
6
|
"""
|
|
7
7
|
Demonstrates using interactive parameters.
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
While this script is running, a UI would typically appear allowing you
|
|
10
10
|
to drag sliders to change the model in real-time. This example sets up
|
|
11
11
|
the backend for that functionality.
|
|
@@ -19,8 +19,8 @@ def main():
|
|
|
19
19
|
p_twist = Param("Twist", 0.0, -10.0, 10.0)
|
|
20
20
|
|
|
21
21
|
# Use the Param objects just like regular numbers.
|
|
22
|
-
scene = box(size=p_size
|
|
23
|
-
|
|
22
|
+
scene = box(size=p_size).round(p_radius).twist(strength=p_twist)
|
|
23
|
+
|
|
24
24
|
# You can also use them in GLSL expressions for animation.
|
|
25
25
|
# Note: `save()` will fail if string expressions are used.
|
|
26
26
|
# animated_box = box(1.0).translate((np.sin(time) * p_size, 0, 0))
|