procfunc 0.30.0__py3-none-any.whl

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 (76) hide show
  1. procfunc/__init__.py +87 -0
  2. procfunc/color.py +57 -0
  3. procfunc/compute_graph/__init__.py +28 -0
  4. procfunc/compute_graph/compute_graph.py +115 -0
  5. procfunc/compute_graph/node.py +200 -0
  6. procfunc/compute_graph/operators_info.py +92 -0
  7. procfunc/compute_graph/proxy.py +173 -0
  8. procfunc/compute_graph/util.py +282 -0
  9. procfunc/context.py +115 -0
  10. procfunc/control.py +174 -0
  11. procfunc/nodes/__init__.py +66 -0
  12. procfunc/nodes/bindings_util.py +196 -0
  13. procfunc/nodes/bpy_node_info.py +280 -0
  14. procfunc/nodes/compositor.py +2242 -0
  15. procfunc/nodes/execute/construct_nodes.py +571 -0
  16. procfunc/nodes/execute/construct_special_cases.py +246 -0
  17. procfunc/nodes/execute/execute.py +548 -0
  18. procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
  19. procfunc/nodes/execute/util.py +247 -0
  20. procfunc/nodes/func.py +1417 -0
  21. procfunc/nodes/geo.py +4240 -0
  22. procfunc/nodes/manifest.json +8769 -0
  23. procfunc/nodes/math.py +644 -0
  24. procfunc/nodes/node_function.py +160 -0
  25. procfunc/nodes/shader.py +2359 -0
  26. procfunc/nodes/types.py +347 -0
  27. procfunc/ops/__init__.py +35 -0
  28. procfunc/ops/_util.py +275 -0
  29. procfunc/ops/addons.py +59 -0
  30. procfunc/ops/attr.py +426 -0
  31. procfunc/ops/collection.py +90 -0
  32. procfunc/ops/curve.py +18 -0
  33. procfunc/ops/file.py +126 -0
  34. procfunc/ops/manifest.json +39149 -0
  35. procfunc/ops/mesh.py +1510 -0
  36. procfunc/ops/modifier.py +603 -0
  37. procfunc/ops/object.py +258 -0
  38. procfunc/ops/primitives/__init__.py +31 -0
  39. procfunc/ops/primitives/camera.py +45 -0
  40. procfunc/ops/primitives/curve.py +71 -0
  41. procfunc/ops/primitives/light.py +114 -0
  42. procfunc/ops/primitives/mesh.py +358 -0
  43. procfunc/ops/uv.py +271 -0
  44. procfunc/random.py +247 -0
  45. procfunc/tracer/__init__.py +43 -0
  46. procfunc/tracer/decorator.py +121 -0
  47. procfunc/tracer/patch.py +494 -0
  48. procfunc/tracer/proxy.py +127 -0
  49. procfunc/tracer/trace.py +222 -0
  50. procfunc/transforms/__init__.py +49 -0
  51. procfunc/transforms/cleanup.py +214 -0
  52. procfunc/transforms/convert.py +20 -0
  53. procfunc/transforms/distribution.py +191 -0
  54. procfunc/transforms/extract_materials.py +116 -0
  55. procfunc/transforms/infer_distribution.py +326 -0
  56. procfunc/transforms/parameters.py +15 -0
  57. procfunc/transforms/util.py +35 -0
  58. procfunc/transpiler/__init__.py +24 -0
  59. procfunc/transpiler/bpy_to_computegraph.py +1348 -0
  60. procfunc/transpiler/codegen.py +919 -0
  61. procfunc/transpiler/identifiers.py +595 -0
  62. procfunc/transpiler/main.py +299 -0
  63. procfunc/types.py +380 -0
  64. procfunc/util/__init__.py +0 -0
  65. procfunc/util/bpy_info.py +145 -0
  66. procfunc/util/camera.py +0 -0
  67. procfunc/util/keyframe.py +70 -0
  68. procfunc/util/log.py +96 -0
  69. procfunc/util/manifest.py +121 -0
  70. procfunc/util/pytree.py +343 -0
  71. procfunc/util/teardown.py +37 -0
  72. procfunc-0.30.0.dist-info/METADATA +120 -0
  73. procfunc-0.30.0.dist-info/RECORD +76 -0
  74. procfunc-0.30.0.dist-info/WHEEL +5 -0
  75. procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
  76. procfunc-0.30.0.dist-info/top_level.txt +1 -0
procfunc/ops/object.py ADDED
@@ -0,0 +1,258 @@
1
+ from typing import Literal
2
+
3
+ import bpy
4
+ import numpy as np
5
+
6
+ import procfunc as pf
7
+ from procfunc import types as t
8
+ from procfunc.ops.attr import read_attribute, write_attribute
9
+ from procfunc.util.bpy_info import bpy_nocollide_data_name
10
+
11
+ from ._util import execute_object_op
12
+
13
+
14
+ @pf.tracer.primitive(mutates=["mutates_obj"])
15
+ def set_transform(
16
+ mutates_obj: t.MeshObject,
17
+ location: t.Vector | tuple[float, float, float] | None = None,
18
+ rotation_euler: t.Vector | tuple[float, float, float] | None = None,
19
+ scale: t.Vector | tuple[float, float, float] | None = None,
20
+ ):
21
+ obj = mutates_obj.item()
22
+ if location is not None:
23
+ obj.location = location
24
+ if rotation_euler is not None:
25
+ obj.rotation_euler = rotation_euler
26
+ if scale is not None:
27
+ obj.scale = scale
28
+
29
+ # Update view layer so transform affects matrix_world
30
+ bpy.context.view_layer.update()
31
+
32
+
33
+ @pf.tracer.primitive(mutates=["mutates_obj"])
34
+ def set_material(
35
+ mutates_obj: t.MeshObject,
36
+ material: "pf.Material | None" = None,
37
+ surface: "pf.ProcNode[pf.Shader] | None" = None,
38
+ displacement: "pf.ProcNode[pf.Vector] | None" = None,
39
+ volume: "pf.ProcNode[pf.Shader] | None" = None,
40
+ selection: np.ndarray | None = None,
41
+ ):
42
+ """Assign a material to an object.
43
+
44
+ Args:
45
+ mutates_obj: Blender object to assign material to
46
+ material: Material to assign. If None, constructs one from surface/displacement/volume.
47
+ surface: Shader to assign to the surface.
48
+ displacement: Vector to assign to the displacement.
49
+ volume: Shader to assign to the volume.
50
+ selection: Boolean array with length equal to number of faces.
51
+ If provided, assigns material only to selected faces.
52
+ """
53
+
54
+ if material is None:
55
+ if all(x is None for x in [surface, displacement, volume]):
56
+ raise ValueError(
57
+ "at least one of material, surface, displacement, or volume must be provided"
58
+ )
59
+ material = pf.Material(
60
+ surface=surface, displacement=displacement, volume=volume
61
+ )
62
+
63
+ # mutates_obj.add_dependency(material)
64
+
65
+ obj_bpy = mutates_obj.item()
66
+
67
+ if selection is None:
68
+ while len(obj_bpy.material_slots) > 0:
69
+ execute_object_op(
70
+ operator=bpy.ops.object.material_slot_remove,
71
+ objs=mutates_obj,
72
+ )
73
+ # TODO remove mutates_obj.dependencies
74
+
75
+ orig_slots = len(obj_bpy.material_slots)
76
+ execute_object_op(
77
+ operator=bpy.ops.object.material_slot_add,
78
+ objs=mutates_obj,
79
+ )
80
+ new_slots = len(obj_bpy.material_slots)
81
+ assert new_slots == orig_slots + 1
82
+ target_slot = obj_bpy.material_slots[-1]
83
+ target_slot.material = material.item()
84
+
85
+ if selection is None:
86
+ return mutates_obj
87
+
88
+ assert isinstance(selection, np.ndarray)
89
+ assert len(selection) == len(obj_bpy.data.polygons)
90
+
91
+ if "material_index" in obj_bpy.data.attributes:
92
+ index_arr = read_attribute(mutates_obj, "material_index", domain="FACE")
93
+ else:
94
+ index_arr = np.full(len(obj_bpy.data.polygons), 0, dtype=np.int32)
95
+
96
+ index_arr[selection] = target_slot.slot_index
97
+ write_attribute(
98
+ mutates_obj, index_arr, "material_index", domain="FACE", overwrite=True
99
+ )
100
+
101
+ return mutates_obj
102
+
103
+
104
+ def alias(obj: t.Object) -> t.Object:
105
+ """Create a linked duplicate that shares the same mesh data."""
106
+
107
+ # TODO need to store meshes as dependencies to avoid double free from alias
108
+
109
+ new_obj = bpy.data.objects.new(
110
+ bpy_nocollide_data_name(obj.item(), bpy.data.objects), obj.item().data
111
+ )
112
+ bpy.context.collection.objects.link(new_obj)
113
+ return t.MeshObject(new_obj)
114
+
115
+
116
+ @pf.tracer.primitive(mutates=["mutates_obj"])
117
+ def shade_flat(
118
+ mutates_obj: t.MeshObject,
119
+ keep_sharp_edges: bool = True,
120
+ ) -> None:
121
+ """
122
+ Render faces of object with flat shading
123
+
124
+ Based on bpy.ops.object.shade_flat
125
+ """
126
+ execute_object_op(
127
+ bpy.ops.object.shade_flat,
128
+ objs=mutates_obj,
129
+ keep_sharp_edges=keep_sharp_edges,
130
+ description=shade_flat.__name__,
131
+ )
132
+
133
+
134
+ @pf.tracer.primitive
135
+ def joined(**objects: t.Object) -> t.MeshObject:
136
+ """
137
+ Copies the objects and creates a new object with them merged together
138
+ """
139
+
140
+ if len(objects) < 2:
141
+ raise ValueError(
142
+ f"{joined.__name__} requires at least two objects, got {len(objects)}"
143
+ )
144
+
145
+ clones = [v.clone() for v in objects.values()]
146
+
147
+ execute_object_op(
148
+ bpy.ops.object.join,
149
+ objs=clones,
150
+ description=joined.__name__,
151
+ )
152
+ return t.MeshObject(bpy.context.active_object)
153
+
154
+
155
+ @pf.tracer.primitive(mutates=["mutates_obj_1", "mutates_obj_2"])
156
+ def join(
157
+ mutates_obj_1: t.MeshObject,
158
+ mutates_obj_2: t.MeshObject,
159
+ ) -> None:
160
+ """
161
+ Modifies mutates_obj_1 to point to a joined object of the two, without any copying.
162
+
163
+ mutates_obj_2 is invalidated. TODO: make it safely point to the joined object
164
+ """
165
+
166
+ execute_object_op(
167
+ bpy.ops.object.join,
168
+ active=mutates_obj_1,
169
+ objs=[mutates_obj_2],
170
+ description=join.__name__,
171
+ )
172
+
173
+ # mutates_obj_1.extend_dependencies(mutates_obj_2)
174
+ # mutates_obj_2.invalidate()
175
+
176
+
177
+ def duplicate(
178
+ obj: t.Object,
179
+ linked: bool = False,
180
+ mode: Literal["TRANSLATION", "ROTATION", "RESIZE"] = "TRANSLATION",
181
+ ) -> None:
182
+ """
183
+ Duplicate selected objects
184
+
185
+ Based on bpy.ops.object.duplicate
186
+ """
187
+ bpy.context.view_layer.objects.active = obj.item()
188
+ execute_object_op(
189
+ bpy.ops.object.duplicate,
190
+ objs=[obj],
191
+ linked=linked,
192
+ mode=mode,
193
+ description=duplicate.__name__,
194
+ )
195
+ return t.Object(bpy.context.active_object)
196
+
197
+
198
+ @pf.tracer.primitive(mutates=["mutates_obj"])
199
+ def shade_smooth(
200
+ mutates_obj: t.MeshObject,
201
+ keep_sharp_edges: bool = True,
202
+ ) -> None:
203
+ """
204
+ Render faces of object with smooth shading
205
+
206
+ Based on bpy.ops.object.shade_smooth
207
+ """
208
+ execute_object_op(
209
+ bpy.ops.object.shade_smooth,
210
+ objs=mutates_obj,
211
+ keep_sharp_edges=keep_sharp_edges,
212
+ description=shade_smooth.__name__,
213
+ )
214
+
215
+
216
+ # TODO: convert() for POINTCLOUD, CURVES, GREASEPENCIL ?
217
+
218
+
219
+ @pf.tracer.primitive
220
+ def curve_to_mesh(
221
+ curve: t.CurveObject,
222
+ merge_customdata: bool = True,
223
+ ) -> t.MeshObject:
224
+ """
225
+ Convert curve to mesh
226
+ """
227
+ execute_object_op(
228
+ bpy.ops.object.convert,
229
+ active=curve,
230
+ target="MESH",
231
+ keep_original=True,
232
+ merge_customdata=merge_customdata,
233
+ description=curve_to_mesh.__name__,
234
+ )
235
+ return t.MeshObject(bpy.context.active_object)
236
+
237
+
238
+ @pf.tracer.primitive()
239
+ def mesh_to_curve(
240
+ mesh: t.MeshObject,
241
+ ) -> t.CurveObject:
242
+ """
243
+ Convert mesh to curve
244
+ """
245
+ execute_object_op(
246
+ bpy.ops.object.convert,
247
+ active=mesh,
248
+ target="CURVE",
249
+ keep_original=True,
250
+ description=mesh_to_curve.__name__,
251
+ )
252
+ return t.CurveObject(bpy.context.active_object)
253
+
254
+
255
+ def clear_scene():
256
+ for dstruct in [bpy.data.objects, bpy.data.meshes, bpy.data.materials]:
257
+ for o in dstruct:
258
+ dstruct.remove(o)
@@ -0,0 +1,31 @@
1
+ from .camera import (
2
+ orthographic_camera,
3
+ perspective_camera,
4
+ )
5
+ from .curve import (
6
+ curve_bezier,
7
+ curve_circle,
8
+ curve_line,
9
+ curve_splines,
10
+ )
11
+ from .light import (
12
+ area_lamp,
13
+ point_lamp,
14
+ spot_lamp,
15
+ sun_lamp,
16
+ )
17
+ from .mesh import (
18
+ empty,
19
+ mesh_circle,
20
+ mesh_cone,
21
+ mesh_cube,
22
+ mesh_cylinder,
23
+ mesh_grid,
24
+ mesh_icosphere,
25
+ mesh_line,
26
+ mesh_monkey,
27
+ mesh_plane,
28
+ mesh_single_vertex,
29
+ mesh_torus,
30
+ mesh_uv_sphere,
31
+ )
@@ -0,0 +1,45 @@
1
+ import bpy
2
+
3
+ from procfunc import types as t
4
+
5
+
6
+ def perspective_camera(
7
+ focal_length_mm: float = 50.0,
8
+ clip_start: float = 0.1,
9
+ clip_end: float = 1000.0,
10
+ sensor_width_mm: float = 36.0,
11
+ sensor_height_mm: float | None = None,
12
+ ) -> t.CameraObject:
13
+ bpy.ops.object.camera_add()
14
+ camera = bpy.context.object
15
+
16
+ camera.data.lens = focal_length_mm
17
+ camera.data.clip_start = clip_start
18
+ camera.data.clip_end = clip_end
19
+ camera.data.sensor_width = sensor_width_mm
20
+ if sensor_height_mm is None:
21
+ resx = bpy.context.scene.render.resolution_x
22
+ resy = bpy.context.scene.render.resolution_y
23
+ ratio = resx / resy
24
+ # assert ratio.is_integer(), (ratio, resx, resy)
25
+ camera.data.sensor_height = sensor_width_mm * ratio
26
+ else:
27
+ camera.data.sensor_height = sensor_height_mm
28
+
29
+ return t.CameraObject(camera)
30
+
31
+
32
+ def orthographic_camera(
33
+ scale: float = 1.0,
34
+ clip_start: float = 0.1,
35
+ clip_end: float = 1000.0,
36
+ ) -> t.CameraObject:
37
+ bpy.ops.object.camera_add()
38
+ camera = bpy.context.object
39
+
40
+ camera.data.type = "ORTHO"
41
+ camera.data.ortho_scale = scale
42
+ camera.data.clip_start = clip_start
43
+ camera.data.clip_end = clip_end
44
+
45
+ return t.CameraObject(camera)
@@ -0,0 +1,71 @@
1
+ from typing import Literal
2
+
3
+ import bpy
4
+ import numpy as np
5
+
6
+ from procfunc import types as t
7
+
8
+ SplineType = Literal["POLY", "BEZIER", "NURBS"]
9
+
10
+
11
+ def curve_circle(radius: float = 1.0):
12
+ bpy.ops.curve.primitive_bezier_circle_add(radius=radius)
13
+ return t.CurveObject(bpy.context.active_object)
14
+
15
+
16
+ def curve_bezier():
17
+ bpy.ops.curve.primitive_bezier_curve_add()
18
+ return t.CurveObject(bpy.context.active_object)
19
+
20
+
21
+ def _add_spline(
22
+ curve_data: bpy.types.Curve,
23
+ points: list[t.Vector] | np.ndarray,
24
+ spline_type: SplineType = "POLY",
25
+ ) -> None:
26
+ """Add a single spline with the given points to *curve_data*."""
27
+ spline = curve_data.splines.new(spline_type)
28
+ assert len(points) > 0, "There should be at least one point"
29
+ spline.points.add(len(points) - 1)
30
+ for i, coord in enumerate(points):
31
+ spline.points[i].co = (*coord, 1)
32
+
33
+
34
+ def curve_line(
35
+ points: list[t.Vector] | np.ndarray,
36
+ dimensions="3D",
37
+ resolution_u=12,
38
+ spline_type: SplineType = "POLY",
39
+ ) -> t.CurveObject:
40
+ """Create a curve object containing a single spline through *points*."""
41
+ if dimensions != "3D":
42
+ raise NotImplementedError(
43
+ f"dimensions={dimensions!r} is not yet supported, only '3D' is implemented"
44
+ )
45
+ if resolution_u != 12:
46
+ raise NotImplementedError(
47
+ f"resolution_u={resolution_u!r} is not yet supported, only 12 is implemented"
48
+ )
49
+ curve = bpy.data.curves.new(curve_line.__name__, type="CURVE")
50
+ _add_spline(curve, points, spline_type)
51
+ obj = bpy.data.objects.new(curve_line.__name__, curve)
52
+ bpy.context.scene.collection.objects.link(obj)
53
+ return t.CurveObject(obj)
54
+
55
+
56
+ def curve_splines(
57
+ splines: list[list[t.Vector] | np.ndarray],
58
+ spline_type: SplineType = "POLY",
59
+ ) -> t.CurveObject:
60
+ """Create a curve object containing multiple splines.
61
+
62
+ Args:
63
+ splines: List of point lists, one per spline.
64
+ spline_type: Spline interpolation type for all splines.
65
+ """
66
+ curve = bpy.data.curves.new(curve_splines.__name__, type="CURVE")
67
+ for points in splines:
68
+ _add_spline(curve, points, spline_type)
69
+ obj = bpy.data.objects.new(curve_splines.__name__, curve)
70
+ bpy.context.scene.collection.objects.link(obj)
71
+ return t.CurveObject(obj)
@@ -0,0 +1,114 @@
1
+ from typing import Literal
2
+
3
+ import bpy
4
+ import numpy as np
5
+
6
+ from procfunc import types as t
7
+ from procfunc.tracer import primitive as tracer_primitive
8
+
9
+
10
+ @tracer_primitive
11
+ def point_lamp(
12
+ energy: float = 10.0,
13
+ color: tuple = (1.0, 1.0, 1.0),
14
+ shadow_soft_size: float = 0.0,
15
+ use_contact_shadow: bool = False,
16
+ contact_shadow_distance: float = 0.2,
17
+ contact_shadow_bias: float = 0.03,
18
+ contact_shadow_thickness: float = 0.2,
19
+ ) -> t.LightObject:
20
+ bpy.ops.object.light_add(type="POINT")
21
+ lamp = bpy.context.object
22
+
23
+ lamp.data.energy = energy
24
+ lamp.data.color = color
25
+ lamp.data.shadow_soft_size = shadow_soft_size
26
+ lamp.data.use_contact_shadow = use_contact_shadow
27
+ lamp.data.contact_shadow_distance = contact_shadow_distance
28
+ lamp.data.contact_shadow_bias = contact_shadow_bias
29
+ lamp.data.contact_shadow_thickness = contact_shadow_thickness
30
+
31
+ return t.LightObject(lamp)
32
+
33
+
34
+ @tracer_primitive
35
+ def sun_lamp(
36
+ intensity: float = 1.0,
37
+ color: tuple = (1.0, 1.0, 1.0),
38
+ angle_deg: float = 0.526,
39
+ use_contact_shadow: bool = False,
40
+ contact_shadow_distance: float = 0.2,
41
+ contact_shadow_bias: float = 0.03,
42
+ contact_shadow_thickness: float = 0.2,
43
+ ) -> t.LightObject:
44
+ bpy.ops.object.light_add(type="SUN")
45
+ lamp = bpy.context.object
46
+
47
+ lamp.data.energy = intensity # intentional - blender uses energy for this case
48
+ lamp.data.color = color
49
+ lamp.data.angle = np.deg2rad(angle_deg)
50
+ lamp.data.use_contact_shadow = use_contact_shadow
51
+ lamp.data.contact_shadow_distance = contact_shadow_distance
52
+ lamp.data.contact_shadow_bias = contact_shadow_bias
53
+ lamp.data.contact_shadow_thickness = contact_shadow_thickness
54
+
55
+ return t.LightObject(lamp)
56
+
57
+
58
+ @tracer_primitive
59
+ def spot_lamp(
60
+ energy: float = 10.0,
61
+ color: tuple = (1.0, 1.0, 1.0),
62
+ spot_size_deg: float = 45.0,
63
+ spot_blend: float = 0.15,
64
+ shadow_soft_size: float = 0.0,
65
+ use_contact_shadow: bool = False,
66
+ contact_shadow_distance: float = 0.2,
67
+ contact_shadow_bias: float = 0.03,
68
+ contact_shadow_thickness: float = 0.2,
69
+ ) -> t.LightObject:
70
+ bpy.ops.object.light_add(type="SPOT")
71
+ lamp = bpy.context.object
72
+
73
+ lamp.data.energy = energy
74
+ lamp.data.color = color
75
+ lamp.data.spot_size = np.deg2rad(spot_size_deg)
76
+ lamp.data.spot_blend = spot_blend
77
+ lamp.data.shadow_soft_size = shadow_soft_size
78
+ lamp.data.use_contact_shadow = use_contact_shadow
79
+ lamp.data.contact_shadow_distance = contact_shadow_distance
80
+ lamp.data.contact_shadow_bias = contact_shadow_bias
81
+ lamp.data.contact_shadow_thickness = contact_shadow_thickness
82
+
83
+ return t.LightObject(lamp)
84
+
85
+
86
+ @tracer_primitive
87
+ def area_lamp(
88
+ energy: float = 10.0,
89
+ color: tuple = (1.0, 1.0, 1.0),
90
+ shape: Literal["SQUARE", "RECTANGLE", "ELLIPSE", "DISK"] = "SQUARE",
91
+ size_x: float = 1.0,
92
+ size_y: float = 1.0,
93
+ portal: bool = False,
94
+ use_contact_shadow: bool = False,
95
+ contact_shadow_distance: float = 0.2,
96
+ contact_shadow_bias: float = 0.03,
97
+ contact_shadow_thickness: float = 0.2,
98
+ ) -> t.LightObject:
99
+ bpy.ops.object.light_add(type="AREA")
100
+ lamp = bpy.context.object
101
+
102
+ lamp.data.energy = energy
103
+ lamp.data.color = color
104
+ lamp.data.shape = shape
105
+ lamp.data.size = size_x
106
+ if shape in ["RECTANGLE", "ELLIPSE"]:
107
+ lamp.data.size_y = size_y
108
+ lamp.data.cycles.is_portal = portal
109
+ lamp.data.use_contact_shadow = use_contact_shadow
110
+ lamp.data.contact_shadow_distance = contact_shadow_distance
111
+ lamp.data.contact_shadow_bias = contact_shadow_bias
112
+ lamp.data.contact_shadow_thickness = contact_shadow_thickness
113
+
114
+ return t.LightObject(lamp)