tree-clipper 0.1.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.
File without changes
tree_clipper/common.py ADDED
@@ -0,0 +1,150 @@
1
+ import bpy
2
+
3
+ from types import NoneType
4
+ from typing import Any, Callable, TYPE_CHECKING
5
+ from pathlib import Path
6
+ import tempfile
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from .export_nodes import Exporter
11
+ from .import_nodes import Importer
12
+
13
+ # these fields are in the top level JSON object
14
+ BLENDER_VERSION = "blender_version"
15
+ TREE_CLIPPER_VERSION = "tree_clipper_version"
16
+ CURRENT_TREE_CLIPPER_VERSION = "0.1.0" # tested to match pyproject.toml
17
+ MATERIAL_NAME = "name"
18
+ TREES = "node_trees"
19
+ EXTERNAL = "external"
20
+ SCENES = "scenes"
21
+
22
+ # within each external item
23
+ EXTERNAL_DESCRIPTION = "description"
24
+ EXTERNAL_FIXED_TYPE_NAME = "fixed_type_name"
25
+ EXTERNAL_SCENE_ID = "scene_id"
26
+
27
+ # for every object
28
+ ID = "id" # to reference it from elsewhere
29
+ DATA = "data" # the actual data
30
+ FROM_ROOT = "from_root" # optional, for debugging
31
+
32
+ # every compressed serialization starts with this
33
+ MAGIC_STRING = "TreeClipper::"
34
+
35
+ # to help prevent typos
36
+ BL_RNA = "bl_rna"
37
+ BL_IDNAME = "bl_idname"
38
+ RNA_TYPE = "rna_type" # TODO: another 'forbidden' category?
39
+ DEFAULT_VALUE = "default_value"
40
+ DISPLAY_SHAPE = "display_shape"
41
+ ITEMS = "items"
42
+ NAME = "name"
43
+ PROP_TYPE_BOOLEAN = "BOOLEAN"
44
+ PROP_TYPE_INT = "INT"
45
+ PROP_TYPE_FLOAT = "FLOAT"
46
+ PROP_TYPE_STRING = "STRING"
47
+ PROP_TYPE_ENUM = "ENUM"
48
+ PROP_TYPE_POINTER = "POINTER"
49
+ PROP_TYPE_COLLECTION = "COLLECTION"
50
+ SIMPLE_PROPERTY_TYPES_AS_STRS = set(
51
+ [
52
+ PROP_TYPE_BOOLEAN,
53
+ PROP_TYPE_INT,
54
+ PROP_TYPE_FLOAT,
55
+ PROP_TYPE_STRING,
56
+ PROP_TYPE_ENUM,
57
+ ]
58
+ )
59
+ NODE_TREE = "node_tree"
60
+ DIMENSIONS = "dimensions"
61
+
62
+
63
+ # bl_* properties can be dangerous to set
64
+ # https://github.com/Algebraic-UG/tree_clipper/issues/39
65
+ # they should probably be read-only in most cases?
66
+ FORBIDDEN_PROPERTIES = [
67
+ "bl_idname",
68
+ "bl_label",
69
+ "bl_subtype_label",
70
+ "bl_static_type",
71
+ "bl_description",
72
+ "bl_icon",
73
+ "bl_width_default",
74
+ "bl_width_min",
75
+ "bl_width_max",
76
+ "bl_height_default",
77
+ "bl_height_min",
78
+ "bl_height_max",
79
+ "bl_socket_idname",
80
+ ]
81
+
82
+
83
+ def no_clobber(data: dict, key: str | int, value) -> None:
84
+ if key in data:
85
+ raise RuntimeError(f"Clobbering '{key}'")
86
+ data[key] = value
87
+
88
+
89
+ class FromRoot:
90
+ def __init__(self, path: list) -> None:
91
+ self.path = path
92
+
93
+ def add(self, piece: str) -> "FromRoot":
94
+ return FromRoot(self.path + [piece])
95
+
96
+ def add_prop(self, prop: bpy.types.Property) -> "FromRoot":
97
+ return self.add(f"{prop.type} ({prop.identifier})")
98
+
99
+ def to_str(self) -> str:
100
+ return str(" -> ".join(self.path))
101
+
102
+
103
+ def most_specific_type_handled(
104
+ specific_handlers: dict[type, Callable],
105
+ obj: bpy.types.bpy_struct,
106
+ ) -> type:
107
+ # collections are too weird, this is False:
108
+ # type(bpy.data.node_groups['Geometry Nodes'].nodes) == bpy.types.Nodes
109
+ if isinstance(obj, bpy.types.bpy_prop_collection):
110
+ return next(
111
+ (
112
+ ty
113
+ for ty in specific_handlers.keys()
114
+ if ty != NoneType and ty.bl_rna.identifier == obj.bl_rna.identifier # type: ignore
115
+ ),
116
+ NoneType,
117
+ )
118
+
119
+ ty = type(obj)
120
+ while True:
121
+ if ty in specific_handlers.keys():
122
+ return ty
123
+ if len(ty.__bases__) == 0:
124
+ return NoneType
125
+ if len(ty.__bases__) > 1:
126
+ raise RuntimeError(f"multiple inheritence {ty}, unclear what to choose")
127
+ ty = ty.__bases__[0]
128
+
129
+
130
+ GETTER = Callable[[], bpy.types.bpy_struct]
131
+ SERIALIZER = Callable[["Exporter", bpy.types.bpy_struct, FromRoot], dict[str, Any]]
132
+ DESERIALIZER = Callable[["Importer", GETTER, dict, FromRoot], None]
133
+ SIMPLE_DATA_TYPE = list[str] | list[float] | list[int] | str | float | int
134
+ SIMPLE_PROP_TYPE = (
135
+ bpy.types.BoolProperty
136
+ | bpy.types.IntProperty
137
+ | bpy.types.FloatProperty
138
+ | bpy.types.StringProperty
139
+ | bpy.types.EnumProperty
140
+ )
141
+ SIMPLE_PROP_TYPE_TUPLE = (
142
+ bpy.types.BoolProperty,
143
+ bpy.types.IntProperty,
144
+ bpy.types.FloatProperty,
145
+ bpy.types.StringProperty,
146
+ bpy.types.EnumProperty,
147
+ )
148
+ EXTERNAL_SERIALIZATION = dict[str, int | str | None]
149
+
150
+ DEFAULT_FILE = str(Path(tempfile.gettempdir()) / "default.json")
@@ -0,0 +1,122 @@
1
+ import bpy
2
+
3
+ from typing import Type
4
+
5
+ from .common import no_clobber
6
+
7
+
8
+ # TODO: we might need to return a list that fits the Blender version
9
+ KNOWN_POINTABLES = {
10
+ bpy.types.SunLight,
11
+ bpy.types.Texture,
12
+ bpy.types.Object,
13
+ bpy.types.WorkSpace,
14
+ bpy.types.Mesh,
15
+ bpy.types.Text,
16
+ bpy.types.Lattice,
17
+ bpy.types.Material,
18
+ bpy.types.Camera,
19
+ bpy.types.World,
20
+ bpy.types.Volume,
21
+ bpy.types.FreestyleLineStyle,
22
+ bpy.types.MovieClip,
23
+ bpy.types.PointLight,
24
+ bpy.types.LightProbeVolume,
25
+ bpy.types.TextureNodeTree,
26
+ bpy.types.AreaLight,
27
+ bpy.types.VoronoiTexture,
28
+ bpy.types.CompositorNodeTree,
29
+ bpy.types.NoiseTexture,
30
+ bpy.types.Image,
31
+ bpy.types.SpotLight,
32
+ bpy.types.ImageTexture,
33
+ bpy.types.VectorFont,
34
+ bpy.types.ParticleSettings,
35
+ bpy.types.Screen,
36
+ bpy.types.Annotation,
37
+ bpy.types.MagicTexture,
38
+ bpy.types.MetaBall,
39
+ bpy.types.Key,
40
+ bpy.types.MarbleTexture,
41
+ bpy.types.MusgraveTexture,
42
+ bpy.types.StucciTexture,
43
+ bpy.types.WoodTexture,
44
+ bpy.types.DistortedNoiseTexture,
45
+ bpy.types.LightProbeSphere,
46
+ bpy.types.Scene,
47
+ bpy.types.CloudsTexture,
48
+ bpy.types.Brush,
49
+ bpy.types.WindowManager,
50
+ bpy.types.Library,
51
+ bpy.types.Collection,
52
+ bpy.types.Sound,
53
+ bpy.types.NodeTree,
54
+ bpy.types.GreasePencil,
55
+ bpy.types.Curves,
56
+ bpy.types.Armature,
57
+ bpy.types.Light,
58
+ bpy.types.Curve,
59
+ bpy.types.Speaker,
60
+ bpy.types.Action,
61
+ bpy.types.GeometryNodeTree,
62
+ bpy.types.ShaderNodeTree,
63
+ bpy.types.PointCloud,
64
+ bpy.types.LightProbe,
65
+ bpy.types.CacheFile,
66
+ bpy.types.TextCurve,
67
+ bpy.types.BlendTexture,
68
+ bpy.types.Mask,
69
+ bpy.types.PaintCurve,
70
+ bpy.types.LightProbePlane,
71
+ bpy.types.Palette,
72
+ bpy.types.SurfaceCurve,
73
+ }
74
+
75
+
76
+ def add_all_known_pointer_properties(
77
+ *,
78
+ cls: Type[bpy.types.PropertyGroup],
79
+ prefix: str,
80
+ ):
81
+ def get_pointer_property_name(ty: type):
82
+ return f"{prefix}{ty.__name__}"
83
+
84
+ # does this even ever happen
85
+ if not hasattr(cls, "__annotations__"):
86
+ cls.__annotations__ = {}
87
+
88
+ # we store which kind of thing we're pointing to, used in get_pointer
89
+ no_clobber(
90
+ cls.__annotations__,
91
+ "active_ptr_type_name",
92
+ bpy.props.StringProperty(),
93
+ )
94
+
95
+ # now actually register all the properties
96
+ for pointable in KNOWN_POINTABLES:
97
+ no_clobber(
98
+ cls.__annotations__,
99
+ get_pointer_property_name(pointable),
100
+ bpy.props.PointerProperty(type=pointable),
101
+ )
102
+
103
+ # this switches the type we're pointing to and clears all
104
+ def set_active_pointer_type(self, type_name: str):
105
+ self.active_ptr_type_name = type_name
106
+ for ty in KNOWN_POINTABLES:
107
+ setattr(self, get_pointer_property_name(ty), None)
108
+
109
+ # this is needed to display the property
110
+ def get_active_pointer_identifier(self) -> str:
111
+ return f"{prefix}{self.active_ptr_type_name}"
112
+
113
+ # directly return the pointer
114
+ def get_active_pointer(self) -> bpy.types.PointerProperty:
115
+ return getattr(self, self.get_active_pointer_identifier())
116
+
117
+ assert not hasattr(cls, set_active_pointer_type.__name__)
118
+ setattr(cls, set_active_pointer_type.__name__, set_active_pointer_type)
119
+ assert not hasattr(cls, get_active_pointer_identifier.__name__)
120
+ setattr(cls, get_active_pointer_identifier.__name__, get_active_pointer_identifier)
121
+ assert not hasattr(cls, get_active_pointer.__name__)
122
+ setattr(cls, get_active_pointer.__name__, get_active_pointer)