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.
Files changed (101) hide show
  1. {sdforge-0.2.0/sdforge.egg-info → sdforge-0.2.1}/PKG-INFO +4 -1
  2. {sdforge-0.2.0 → sdforge-0.2.1}/examples/camera.py +2 -2
  3. sdforge-0.2.1/examples/constraints.py +213 -0
  4. {sdforge-0.2.0 → sdforge-0.2.1}/examples/debug.py +23 -7
  5. {sdforge-0.2.0 → sdforge-0.2.1}/examples/export.py +1 -1
  6. {sdforge-0.2.0 → sdforge-0.2.1}/examples/group.py +2 -2
  7. {sdforge-0.2.0 → sdforge-0.2.1}/examples/light.py +3 -3
  8. sdforge-0.2.1/examples/material.py +65 -0
  9. {sdforge-0.2.0 → sdforge-0.2.1}/examples/noise.py +2 -2
  10. sdforge-0.2.1/examples/operations.py +149 -0
  11. {sdforge-0.2.0 → sdforge-0.2.1}/examples/params.py +3 -3
  12. sdforge-0.2.1/examples/primitives.py +130 -0
  13. {sdforge-0.2.0 → sdforge-0.2.1}/examples/render.py +1 -1
  14. sdforge-0.2.1/examples/saving.py +166 -0
  15. {sdforge-0.2.0 → sdforge-0.2.1}/examples/shaping.py +34 -2
  16. sdforge-0.2.1/examples/sketch.py +99 -0
  17. {sdforge-0.2.0 → sdforge-0.2.1}/examples/transforms.py +51 -35
  18. sdforge-0.2.1/sdforge/__init__.py +14 -0
  19. sdforge-0.2.1/sdforge/api/camera.py +29 -0
  20. sdforge-0.2.1/sdforge/api/constraints.py +277 -0
  21. sdforge-0.2.1/sdforge/api/core.py +618 -0
  22. sdforge-0.2.1/sdforge/api/cpu.py +748 -0
  23. sdforge-0.2.1/sdforge/api/debug.py +28 -0
  24. {sdforge-0.2.0/sdforge → sdforge-0.2.1/sdforge/api}/export.py +1 -1
  25. sdforge-0.2.1/sdforge/api/forge.py +45 -0
  26. sdforge-0.2.1/sdforge/api/gpu.py +163 -0
  27. sdforge-0.2.1/sdforge/api/group.py +51 -0
  28. sdforge-0.2.1/sdforge/api/light.py +32 -0
  29. sdforge-0.2.1/sdforge/api/loader.py +69 -0
  30. sdforge-0.2.1/sdforge/api/material.py +62 -0
  31. sdforge-0.2.1/sdforge/api/mesh.py +403 -0
  32. sdforge-0.2.1/sdforge/api/noise.py +35 -0
  33. sdforge-0.2.1/sdforge/api/operations.py +167 -0
  34. sdforge-0.2.1/sdforge/api/params.py +76 -0
  35. sdforge-0.2.1/sdforge/api/primitives.py +544 -0
  36. sdforge-0.2.1/sdforge/api/render.py +441 -0
  37. sdforge-0.2.1/sdforge/api/shaping.py +95 -0
  38. sdforge-0.2.1/sdforge/api/sketch.py +121 -0
  39. sdforge-0.2.1/sdforge/api/transforms.py +209 -0
  40. sdforge-0.2.1/sdforge/api/utils.py +55 -0
  41. sdforge-0.2.1/sdforge/glsl/debug.glsl +34 -0
  42. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/noise.glsl +8 -0
  43. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/operations.glsl +33 -1
  44. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/primitives.glsl +71 -42
  45. sdforge-0.2.1/sdforge/glsl/raymarching.glsl +39 -0
  46. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/shaping.glsl +1 -1
  47. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/transforms.glsl +18 -23
  48. {sdforge-0.2.0 → sdforge-0.2.1/sdforge.egg-info}/PKG-INFO +4 -1
  49. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/SOURCES.txt +27 -7
  50. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/requires.txt +4 -0
  51. {sdforge-0.2.0 → sdforge-0.2.1}/setup.py +6 -2
  52. sdforge-0.2.1/tests/__init__.py +0 -0
  53. sdforge-0.2.1/tests/conftest.py +81 -0
  54. sdforge-0.2.1/tests/constraints.py +267 -0
  55. {sdforge-0.2.0 → sdforge-0.2.1}/tests/debug.py +54 -8
  56. {sdforge-0.2.0 → sdforge-0.2.1}/tests/export.py +2 -2
  57. {sdforge-0.2.0 → sdforge-0.2.1}/tests/forge.py +18 -26
  58. {sdforge-0.2.0 → sdforge-0.2.1}/tests/group.py +3 -3
  59. sdforge-0.2.1/tests/material.py +77 -0
  60. sdforge-0.2.1/tests/noise.py +64 -0
  61. {sdforge-0.2.0 → sdforge-0.2.1}/tests/operations.py +95 -16
  62. sdforge-0.2.1/tests/primitives.py +241 -0
  63. sdforge-0.2.1/tests/render.py +172 -0
  64. sdforge-0.2.1/tests/saving.py +187 -0
  65. {sdforge-0.2.0 → sdforge-0.2.1}/tests/shaping.py +40 -13
  66. sdforge-0.2.1/tests/sketch.py +112 -0
  67. sdforge-0.2.1/tests/transforms.py +378 -0
  68. sdforge-0.2.0/examples/material.py +0 -23
  69. sdforge-0.2.0/examples/operations.py +0 -71
  70. sdforge-0.2.0/examples/primitives.py +0 -68
  71. sdforge-0.2.0/examples/saving.py +0 -79
  72. sdforge-0.2.0/sdforge/__init__.py +0 -30
  73. sdforge-0.2.0/sdforge/core.py +0 -340
  74. sdforge-0.2.0/sdforge/debug.py +0 -13
  75. sdforge-0.2.0/sdforge/glsl/debug.glsl +0 -15
  76. sdforge-0.2.0/sdforge/glsl/raymarching.glsl +0 -23
  77. sdforge-0.2.0/sdforge/loader.py +0 -32
  78. sdforge-0.2.0/sdforge/mesh.py +0 -150
  79. sdforge-0.2.0/sdforge/render.py +0 -309
  80. sdforge-0.2.0/sdforge/utils.py +0 -8
  81. sdforge-0.2.0/tests/conftest.py +0 -147
  82. sdforge-0.2.0/tests/material.py +0 -48
  83. sdforge-0.2.0/tests/noise.py +0 -50
  84. sdforge-0.2.0/tests/primitives.py +0 -144
  85. sdforge-0.2.0/tests/render.py +0 -95
  86. sdforge-0.2.0/tests/saving.py +0 -78
  87. sdforge-0.2.0/tests/transforms.py +0 -124
  88. {sdforge-0.2.0 → sdforge-0.2.1}/LICENSE +0 -0
  89. {sdforge-0.2.0 → sdforge-0.2.1}/MANIFEST.in +0 -0
  90. {sdforge-0.2.0 → sdforge-0.2.1}/README.md +0 -0
  91. {sdforge-0.2.0 → sdforge-0.2.1}/examples/__init__.py +0 -0
  92. {sdforge-0.2.0 → sdforge-0.2.1}/examples/forge.py +0 -0
  93. {sdforge-0.2.0/tests → sdforge-0.2.1/sdforge/api}/__init__.py +0 -0
  94. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/camera.glsl +0 -0
  95. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge/glsl/light.glsl +0 -0
  96. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/dependency_links.txt +0 -0
  97. {sdforge-0.2.0 → sdforge-0.2.1}/sdforge.egg-info/top_level.txt +0 -0
  98. {sdforge-0.2.0 → sdforge-0.2.1}/setup.cfg +0 -0
  99. {sdforge-0.2.0 → sdforge-0.2.1}/tests/camera.py +0 -0
  100. {sdforge-0.2.0 → sdforge-0.2.1}/tests/light.py +0 -0
  101. {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.0
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, radius=0.1) | sphere(1.2)
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, radius=0.1) | sphere(1.2)
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, radius=0.1) - sphere(1.2)
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, radius=0.1) - sphere(1.2)
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, radius=0.1) - sphere(p_radius)
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), radius=0.1).translate((-1, 0, 0))
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, radius=0.1) | sphere(1.2)
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, radius=0.1) | sphere(1.2)
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(r=1.2)
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, radius=0.1)
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, radius=p_radius).twist(p_twist)
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))