skelform-python 0.1.0__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.
@@ -0,0 +1 @@
1
+ /dist
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skelform_python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: SkelForm runtime for Python
5
5
  Author-email: Retropaint <darkglasses1122@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "skelform_python"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  { name="Retropaint", email="darkglasses1122@gmail.com" },
10
10
  ]
@@ -0,0 +1,343 @@
1
+ import math
2
+ import copy
3
+ import zipfile
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class Vec2:
10
+ x: float
11
+ y: float
12
+
13
+ def __sub__(self, other):
14
+ return Vec2(self.x - other.x, self.y - other.y)
15
+
16
+ def __add__(self, other):
17
+ return Vec2(self.x + other.x, self.y + other.y)
18
+
19
+ def __mul__(self, other):
20
+ return Vec2(self.x * other.x, self.y * other.y)
21
+
22
+ def __isub__(self, other):
23
+ return self.__sub__(other)
24
+
25
+ def __iadd__(self, other):
26
+ return self.__add__(other)
27
+
28
+ def __imul__(self, other):
29
+ return self.__mul__(other)
30
+
31
+
32
+ @dataclass
33
+ class Bone:
34
+ name: str
35
+ id: int
36
+ parent_id: int
37
+ style_ids: Optional[list[int]]
38
+ tex: Optional[str]
39
+ rot: float
40
+ scale: Vec2
41
+ pos: Vec2
42
+ ik_bone_ids: Optional[list[int]]
43
+ ik_mode: Optional[int]
44
+ ik_constraint_str: Optional[str]
45
+ ik_constraint: Optional[int]
46
+ ik_family_id: Optional[int]
47
+ ik_target_id: Optional[int]
48
+ init_rot: float
49
+ init_scale: Vec2
50
+ init_pos: Vec2
51
+ init_ik_constraint: Optional[int]
52
+ zindex: Optional[int] = 0
53
+
54
+
55
+ @dataclass
56
+ class Keyframe:
57
+ frame: int
58
+ bone_id: int
59
+ element: int
60
+ value: float
61
+
62
+
63
+ @dataclass
64
+ class Animation:
65
+ name: str
66
+ keyframes: list[Keyframe]
67
+ fps: int
68
+
69
+
70
+ @dataclass
71
+ class Texture:
72
+ name: str
73
+ offset: Vec2
74
+ size: Vec2
75
+ atlas_idx: int
76
+
77
+
78
+ @dataclass
79
+ class Style:
80
+ name: str
81
+ textures: list[Texture]
82
+
83
+
84
+ @dataclass
85
+ class Atlas:
86
+ filename: str
87
+ size: Vec2
88
+
89
+
90
+ @dataclass
91
+ class Armature:
92
+ bones: list[Bone]
93
+ ik_root_ids: list[int]
94
+ animations: Optional[list[Animation]]
95
+ atlases: list[Atlas]
96
+ styles: list[Style]
97
+
98
+
99
+ def animate(
100
+ armature: Armature, animations: [Animation], frames: [int], blend_frames: [int]
101
+ ):
102
+ bones = []
103
+ for a in range(len(animations)):
104
+ kf = animations[a].keyframes
105
+ bf = blend_frames[a]
106
+ ikf = interpolate_keyframes
107
+
108
+ for bone in armature.bones:
109
+ bone = copy.deepcopy(bone)
110
+ bones.append(bone)
111
+ id = bone.id
112
+ # yapf: disable
113
+ bone.pos.x = ikf(0, bone.pos.x, bone.init_pos.x, kf, frames[a], id, bf)
114
+ bone.pos.y = ikf(1, bone.pos.y, bone.init_pos.y, kf, frames[a], id, bf)
115
+ bone.rot = ikf(2, bone.rot, bone.init_rot, kf, frames[a], id, bf)
116
+ bone.scale.x = ikf(3, bone.scale.x, bone.init_scale.x, kf, frames[a], id, bf)
117
+ bone.scale.y = ikf(4, bone.scale.y, bone.init_scale.y, kf, frames[a], id, bf)
118
+
119
+ return bones
120
+
121
+
122
+ def rotate(point: Vec2, rot: float):
123
+ return Vec2(
124
+ point.x * math.cos(rot) - point.y * math.sin(rot),
125
+ point.x * math.sin(rot) + point.y * math.cos(rot),
126
+ )
127
+
128
+
129
+ def inheritance(bones, ik_rots):
130
+ for bone in bones:
131
+ if bone.parent_id != -1:
132
+ # inherit parent
133
+ parent = bones[bone.parent_id]
134
+
135
+ bone.rot += parent.rot
136
+ bone.scale *= parent.scale
137
+ bone.pos *= parent.scale
138
+
139
+ bone.pos = rotate(bone.pos, parent.rot)
140
+
141
+ bone.pos += parent.pos
142
+
143
+ if bone.id in ik_rots:
144
+ bone.rot = ik_rots[bone.id]
145
+
146
+ return bones
147
+
148
+
149
+ def magnitude(vec):
150
+ return math.sqrt(vec.x * vec.x + vec.y * vec.y)
151
+
152
+
153
+ def normalize(vec):
154
+ mag = magnitude(vec)
155
+ if mag == 0:
156
+ return Vec2(0, 0)
157
+ return Vec2(vec.x / mag, vec.y / mag)
158
+
159
+
160
+ def construct(armature: Armature):
161
+ inh_props = copy.deepcopy(armature.bones)
162
+
163
+ inh_props = inheritance(inh_props, {})
164
+ ik_rots = inverse_kinematics(inh_props, armature.ik_root_ids)
165
+
166
+ final_bones = copy.deepcopy(armature.bones)
167
+ final_bones = inheritance(final_bones, ik_rots)
168
+
169
+ return final_bones
170
+
171
+
172
+ def inverse_kinematics(bones: list[Bone], ik_root_ids: list[int]):
173
+ ik_rots = {}
174
+
175
+ for root_id in ik_root_ids:
176
+ family = bones[root_id]
177
+ if (
178
+ family.ik_target_id == -1
179
+ or not family.ik_bone_ids
180
+ or not family.ik_target_id
181
+ ):
182
+ continue
183
+
184
+ root = copy.deepcopy(bones[family.ik_bone_ids[0]].pos)
185
+ target = copy.deepcopy(bones[family.ik_target_id].pos)
186
+
187
+ for i in range(10):
188
+ fabrik(family, bones, root, target)
189
+
190
+ # setting bone rotations
191
+ end_bone = bones[family.ik_bone_ids[-1]].pos
192
+ tip_pos = end_bone
193
+ for i in range(len(family.ik_bone_ids) - 1, -1, -1):
194
+ dir = tip_pos - bones[family.ik_bone_ids[i]].pos
195
+ tip_pos = bones[family.ik_bone_ids[i]].pos
196
+ bones[family.ik_bone_ids[i]].rot = math.atan2(dir.y, dir.x)
197
+
198
+ # applying constraint
199
+ joint_dir = normalize(bones[family.ik_bone_ids[1]].pos - root)
200
+ base_dir = normalize(target - root)
201
+ dir = joint_dir.x * base_dir.y - base_dir.x * joint_dir.y
202
+ base_angle = math.atan2(base_dir.y, base_dir.x)
203
+ cw = family.ik_constraint == 1 and dir > 0
204
+ ccw = family.ik_constraint == 2 and dir < 0
205
+ if ccw or cw:
206
+ for i in family.ik_bone_ids:
207
+ bones[i].rot = -bones[i].rot + base_angle * 2
208
+
209
+ # saving rotations to map
210
+ for i in range(len(family.ik_bone_ids) - 1):
211
+ ik_rots[family.ik_bone_ids[i]] = bones[family.ik_bone_ids[i]].rot
212
+
213
+ return ik_rots
214
+
215
+
216
+ def fabrik(family, bones, root, target):
217
+ # forward reaching
218
+ next_pos = bones[family.ik_target_id].pos
219
+ next_length = 0
220
+ for i in range(len(family.ik_bone_ids) - 1, -1, -1):
221
+ length = Vec2(0, 0)
222
+ if i != len(family.ik_bone_ids) - 1:
223
+ length = normalize(next_pos - bones[family.ik_bone_ids[i]].pos)
224
+ length.x *= next_length
225
+ length.y *= next_length
226
+
227
+ if i != 0:
228
+ next_bone = bones[family.ik_bone_ids[i - 1]]
229
+ bone_pos = bones[family.ik_bone_ids[i]].pos
230
+ next_length = magnitude(bone_pos - next_bone.pos)
231
+
232
+ bones[family.ik_bone_ids[i]].pos = next_pos - length
233
+ next_pos = bones[family.ik_bone_ids[i]].pos
234
+
235
+ # backward reaching
236
+ prev_pos = root
237
+ prev_length = 0
238
+ for i in range(len(family.ik_bone_ids)):
239
+ length = Vec2(0, 0)
240
+ if i != 0:
241
+ length = normalize(prev_pos - bones[family.ik_bone_ids[i]].pos)
242
+ length.x *= prev_length
243
+ length.y *= prev_length
244
+
245
+ if i != len(family.ik_bone_ids) - 1:
246
+ prev_bone = bones[family.ik_bone_ids[i + 1]]
247
+ bone_pos = bones[family.ik_bone_ids[i]].pos
248
+ prev_length = magnitude(bone_pos - prev_bone.pos)
249
+
250
+ bones[family.ik_bone_ids[i]].pos = prev_pos - length
251
+ prev_pos = bones[family.ik_bone_ids[i]].pos
252
+
253
+
254
+ # Flips bone's rotation if either axis of provided scale is negative.
255
+ # Returns new bone rotations
256
+ def check_bone_flip(bone_rot: float, scale: Vec2):
257
+ either = scale.x < 0 or scale.y < 0
258
+ both = scale.x < 0 and scale.y < 0
259
+ if either and not both:
260
+ bone_rot = -bone_rot
261
+ return bone_rot
262
+
263
+
264
+ # Returns a (bone.id, Texture) map of textures to draw bones with.
265
+ def setup_bone_textures(bones: [Bone], styles: [Style]):
266
+ final_textures = {}
267
+ for bone in bones:
268
+ for style in styles:
269
+ if bone.tex is None:
270
+ continue
271
+ final_tex = {}
272
+ has_final = False
273
+ for tex in style.textures:
274
+ if tex.name == bone.tex:
275
+ final_tex = tex
276
+ has_final = True
277
+ break
278
+ if has_final:
279
+ final_textures[bone.id] = final_tex
280
+
281
+ return final_textures
282
+
283
+
284
+ def interpolate_keyframes(
285
+ element, field, default, keyframes, frame, bone_id, blend_frames
286
+ ):
287
+ prev_kf = {}
288
+ next_kf = {}
289
+
290
+ for kf in keyframes:
291
+ if kf.frame < frame and kf.bone_id == bone_id and kf.element == element:
292
+ prev_kf = kf
293
+
294
+ for kf in keyframes:
295
+ if kf.frame >= frame and kf.bone_id == bone_id and kf.element == element:
296
+ next_kf = kf
297
+ break
298
+
299
+ if prev_kf == {}:
300
+ prev_kf = next_kf
301
+ elif next_kf == {}:
302
+ next_kf = prev_kf
303
+
304
+ if prev_kf == {} and next_kf == {}:
305
+ return interpolate(frame, blend_frames, field, default)
306
+
307
+ total_frames = next_kf.frame - prev_kf.frame
308
+ current_frame = frame - prev_kf.frame
309
+
310
+ result = interpolate(current_frame, total_frames, prev_kf.value, next_kf.value)
311
+ blend = interpolate(current_frame, blend_frames, field, result)
312
+
313
+ return blend
314
+
315
+
316
+ def interpolate(current, max, start_val, end_val):
317
+ if current > max or max == 0:
318
+ return end_val
319
+ interp = current / max
320
+ end = end_val - start_val
321
+ return start_val + (end * interp)
322
+
323
+
324
+ def format_frame(frame, animation: Animation, reverse, loop):
325
+ last_kf = len(animation.keyframes) - 1
326
+ last_frame = animation.keyframes[last_kf].frame
327
+
328
+ if loop:
329
+ frame %= last_frame + 1
330
+
331
+ if reverse:
332
+ frame = last_frame - frame
333
+
334
+ return int(frame)
335
+
336
+
337
+ def time_frame(time, animation, reverse, loop):
338
+ frametime = 1 / animation.fps
339
+ frame = time / frametime
340
+
341
+ frame = format_frame(frame, animation, reverse, loop)
342
+
343
+ return int(frame)
@@ -0,0 +1,143 @@
1
+ import zipfile
2
+ import json
3
+ import sys
4
+ from typing import List
5
+ from types import SimpleNamespace
6
+ import math
7
+
8
+ sys.path.append("../../skelform_python")
9
+
10
+ import skelform_python
11
+
12
+
13
+ def new_bone(id, x, y):
14
+ return SimpleNamespace(id=id, pos=SimpleNamespace(x=x, y=y))
15
+
16
+
17
+ def setup_armature():
18
+ armature = SimpleNamespace(bones=[], ik_families=[])
19
+
20
+ armature.bones.append(new_bone(0, 0, 150))
21
+ armature.bones.append(new_bone(1, 0, 0))
22
+ armature.bones.append(new_bone(2, 50, 0))
23
+ armature.bones.append(new_bone(3, 100, 0))
24
+
25
+ armature.ik_families.append(
26
+ SimpleNamespace(target_id=0, constraint="Clockwise", bone_ids=[1, 2, 3])
27
+ )
28
+
29
+ return armature
30
+
31
+
32
+ def forward_reaching(bones, ik_families):
33
+ for family in ik_families:
34
+ if family.target_id == -1:
35
+ continue
36
+ next_pos = bones[family.target_id].pos
37
+ next_length = 0
38
+ for i in range(len(family.bone_ids) - 1, -1, -1):
39
+ length = skelform_python.Vec2(0, 0)
40
+ if i != len(family.bone_ids) - 1:
41
+ length = skelform_python.normalize(
42
+ skelform_python.vec_sub(next_pos, bones[family.bone_ids[i]].pos)
43
+ )
44
+ length.x *= next_length
45
+ length.y *= next_length
46
+
47
+ if i != 0:
48
+ next_bone = bones[family.bone_ids[i - 1]]
49
+ next_length = skelform_python.magnitude(
50
+ skelform_python.vec_sub(
51
+ bones[family.bone_ids[i]].pos, next_bone.pos
52
+ )
53
+ )
54
+
55
+ bones[family.bone_ids[i]].pos = skelform_python.vec_sub(next_pos, length)
56
+ next_pos = bones[family.bone_ids[i]].pos
57
+ print(f"{next_pos.x:.2f}", f"{next_pos.y:.2f}")
58
+
59
+
60
+ def backward_reaching(bones, ik_families, root):
61
+ for family in ik_families:
62
+ base_line = skelform_python.normalize(
63
+ skelform_python.vec_sub(bones[family.target_id].pos, root)
64
+ )
65
+ base_angle = math.atan2(base_line.y, base_line.x)
66
+ if family.target_id == -1:
67
+ continue
68
+ next_pos = root
69
+ next_length = 0
70
+ for i in range(len(family.bone_ids)):
71
+ length = skelform_python.Vec2(0, 0)
72
+ if i != 0:
73
+ length = skelform_python.normalize(
74
+ skelform_python.vec_sub(next_pos, bones[family.bone_ids[i]].pos)
75
+ )
76
+ length.x *= next_length
77
+ length.y *= next_length
78
+
79
+ if i != len(family.bone_ids) - 1:
80
+ next_bone = bones[family.bone_ids[i + 1]]
81
+ next_length = skelform_python.magnitude(
82
+ skelform_python.vec_sub(
83
+ bones[family.bone_ids[i]].pos, next_bone.pos
84
+ )
85
+ )
86
+
87
+ bones[family.bone_ids[i]].pos = skelform_python.vec_sub(next_pos, length)
88
+
89
+ if i != 0 and i != len(family.bone_ids) - 1 and family.constraint != "None":
90
+ joint_line = skelform_python.normalize(
91
+ skelform_python.vec_sub(next_pos, bones[family.bone_ids[i]].pos)
92
+ )
93
+ joint_angle = math.atan2(joint_line.y, joint_line.x) - base_angle
94
+
95
+ constraint_min = 0
96
+ constraint_max = 0
97
+ if family.constraint == "Clockwise":
98
+ constraint_min = -3.14
99
+ else:
100
+ constraint_max = 3.14
101
+
102
+ if joint_angle > constraint_max or joint_angle < constraint_min:
103
+ push_angle = -joint_angle * 2
104
+ new_point = skelform_python.rotate(
105
+ skelform_python.vec_sub(
106
+ bones[family.bone_ids[i]].pos, next_pos
107
+ ),
108
+ push_angle,
109
+ )
110
+ bones[family.bone_ids[i]].pos = skelform_python.vec_add(
111
+ new_point, next_pos
112
+ )
113
+
114
+ next_pos = bones[family.bone_ids[i]].pos
115
+ print(f"{next_pos.x:.2f}", f"{next_pos.y:.2f}")
116
+
117
+
118
+ def rotations(bones, ik_families):
119
+ for family in ik_families:
120
+ end_bone = bones[family.bone_ids[-1]].pos
121
+ tip_pos = end_bone
122
+ for i in range(len(family.bone_ids) - 1, -1, -1):
123
+ dir = skelform_python.vec_sub(tip_pos, bones[family.bone_ids[i]].pos)
124
+ tip_pos = bones[family.bone_ids[i]].pos
125
+ angle = math.atan2(dir.y, dir.x)
126
+ print(f"{angle:.2f}", f"{angle * 180 / 3.14:.2f}")
127
+
128
+
129
+ armature = setup_armature()
130
+
131
+ root = armature.bones[armature.ik_families[0].bone_ids[0]].pos
132
+
133
+ print()
134
+ print("forward reaching:")
135
+ forward_reaching(armature.bones, armature.ik_families)
136
+ print()
137
+
138
+ print("backward reaching:")
139
+ backward_reaching(armature.bones, armature.ik_families, root)
140
+ print()
141
+
142
+ print("rotations:")
143
+ rotations(armature.bones, armature.ik_families)
@@ -0,0 +1,8 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.9"
4
+
5
+ [[package]]
6
+ name = "skelform-python"
7
+ version = "0.1.0.1"
8
+ source = { editable = "." }
@@ -1,102 +0,0 @@
1
- import math
2
- import copy
3
- import pytweening
4
- import zipfile
5
-
6
- def get_frame_by_time(armature, anim_idx, elapsed, reverse):
7
- anim = armature["animations"][anim_idx]
8
- last_frame = anim["keyframes"][-1]["frame"]
9
-
10
- frametime = 1 / anim["fps"]
11
- frame = elapsed / frametime
12
-
13
- if reverse:
14
- frame = last_frame - frame
15
-
16
- return frame
17
-
18
-
19
- def animate(armature, anim_idx, frame, after_animate=None):
20
- props = []
21
- keyframes = armature["animations"][anim_idx]["keyframes"]
22
-
23
- frame %= keyframes[-1]["frame"]
24
-
25
- for bone in armature["bones"]:
26
- prop = copy.deepcopy(bone)
27
- props.append(prop)
28
-
29
- # interpolate
30
- # yapf: disable
31
- prop["rot"] += animate_float(keyframes, frame, prop["id"], "Rotation", 0)
32
- prop["pos"]["x"] += animate_float(keyframes, frame, prop["id"], "PositionX", 0)
33
- prop["pos"]["y"] += animate_float(keyframes, frame, prop["id"], "PositionY", 0)
34
- prop["scale"]["x"] *= animate_float(keyframes, frame, prop["id"], "ScaleX", 1)
35
- prop["scale"]["y"] *= animate_float(keyframes, frame, prop["id"], "ScaleY", 1)
36
-
37
- try:
38
- after_animate(props, prop)
39
- except:
40
- pass
41
-
42
- if prop["parent_id"] == -1:
43
- continue
44
-
45
- # inherit parent
46
- parent = [prop for prop in props if prop["id"] == props[-1]["parent_id"]][0]
47
-
48
- prop["rot"] += parent["rot"]
49
- prop["scale"]["x"] *= parent["scale"]["x"]
50
- prop["scale"]["y"] *= parent["scale"]["y"]
51
- prop["pos"]["x"] *= parent["scale"]["x"]
52
- prop["pos"]["y"] *= parent["scale"]["y"]
53
-
54
- x = copy.deepcopy(prop["pos"]["x"])
55
- y = copy.deepcopy(prop["pos"]["y"])
56
- prop["pos"]["x"] = x * math.cos(parent["rot"]) - y * math.sin(parent["rot"])
57
- prop["pos"]["y"] = x * math.sin(parent["rot"]) + y * math.cos(parent["rot"])
58
-
59
- prop["pos"]["x"] += parent["pos"]["x"]
60
- prop["pos"]["y"] += parent["pos"]["y"]
61
-
62
- return props
63
-
64
-
65
- def animate_float(keyframes, frame, bone_id, element, default):
66
- prev_kf = {}
67
- next_kf = {}
68
-
69
- for kf in keyframes:
70
- if kf["frame"] > frame:
71
- break
72
- elif kf["bone_id"] == bone_id and kf["element"] == element:
73
- prev_kf = kf
74
-
75
- for kf in keyframes:
76
- if (
77
- kf["frame"] >= frame
78
- and kf["bone_id"] == bone_id
79
- and kf["element"] == element
80
- ):
81
- next_kf = kf
82
- break
83
-
84
- if prev_kf == {}:
85
- prev_kf = next_kf
86
- elif next_kf == {}:
87
- next_kf = prev_kf
88
-
89
- if prev_kf == {} and next_kf == {}:
90
- return default
91
-
92
- total_frames = next_kf["frame"] - prev_kf["frame"]
93
- current_frame = frame - prev_kf["frame"]
94
-
95
- if total_frames == 0:
96
- return prev_kf["value"]
97
-
98
- interp = current_frame / total_frames
99
- start = prev_kf["value"]
100
- end = next_kf["value"] - prev_kf["value"]
101
- result = start + (end * interp)
102
- return result