Skip to content

Commit

Permalink
update JSON export structure
Browse files Browse the repository at this point in the history
  • Loading branch information
timhendriks93 committed Jan 24, 2024
1 parent 8ff5684 commit 39e316b
Show file tree
Hide file tree
Showing 12 changed files with 1,689 additions and 746 deletions.
2 changes: 0 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"files.exclude": {
"**/__pycache__": true
},
Expand Down
8 changes: 4 additions & 4 deletions addon/ops/arduino_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class ArduinoExport(Operator, BaseExport, ExportHelper):
bl_idname = "export_anim.servo_positions_arduino"
bl_label = "Animation Servo Positions (.h)"
bl_idname = "export_anim.servo_animation_arduino"
bl_label = "Servo Animation (.h)"
bl_description = "Save an Arduino header file with servo position values of the active armature"

filename_ext = ".h"
Expand Down Expand Up @@ -49,11 +49,11 @@ def export(self, positions, filepath, context):
content = (
"/*\n Blender Servo Animation Positions\n\n "
f"FPS: {fps}\n Frames: {frames}\n Seconds: {seconds}\n "
f"Bones: {len(positions)}\n Armature: {context.object.name}\n "
f"Bones: {len(positions[0])}\n Armature: {context.object.name}\n "
f"Scene: {context.scene.name}\n File: {filename}\n*/\n"
)

commands = self.get_commands(frames, positions)
commands = self.get_commands(positions)
length = len(commands)
lines = self.join_by_chunk_size(commands, self.chunk_size)
progmem = 'PROGMEM ' if self.progmem else ''
Expand Down
22 changes: 5 additions & 17 deletions addon/ops/base_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def execute(self, context):
bpy.ops.servo_animation.stop_live_mode()

try:
positions = calculate_positions(context)
positions = calculate_positions(context, self.skip_duplicates)
self.export(positions, self.filepath, context)
except RuntimeError as error:
self.report({'ERROR'}, str(error))
Expand All @@ -53,25 +53,13 @@ def execute(self, context):

return {'FINISHED'}

def get_commands(self, frames, positions):
def get_commands(self, positions):
commands = []
last_positions = {}

for frame in range(frames):
for servo_id in range(255):
if servo_id not in positions:
continue

if servo_id not in last_positions:
last_positions[servo_id] = None

position = positions[servo_id][frame]

if self.skip_duplicates and last_positions[servo_id] == position:
continue

for frame_positions in positions:
for servo_id in frame_positions:
position = frame_positions[servo_id]
commands += self.get_command(servo_id, position)
last_positions[servo_id] = position

commands.append(self.LINE_BREAK)

Expand Down
35 changes: 20 additions & 15 deletions addon/ops/json_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from .base_export import BaseExport
from ..utils.servo_settings import get_pose_bone_by_servo_id


class JsonExport(Operator, BaseExport, ExportHelper):
bl_idname = "export_anim.servo_positions_json"
bl_label = "Animation Servo Positions (.json)"
bl_idname = "export_anim.servo_animation_json"
bl_label = "Servo Animation (.json)"
bl_description = "Save a JSON file with servo position values of the active armature"

filename_ext = ".json"
Expand All @@ -20,34 +19,40 @@ class JsonExport(Operator, BaseExport, ExportHelper):
maxlen=255
)

skip_duplicates: None
indent: bpy.props.EnumProperty(
name="Indent",
items=[
('None', 'No indent', ''),
('1', '1 Space', ''),
('2', '2 Spaces', ''),
('3', '3 Spaces', ''),
('4', '4 Spaces', ''),
],
default='2',
)

def export(self, positions, filepath, context):
fps, frames, seconds = self.get_time_meta(context.scene)
filename = self.get_blend_filename()

servos = {}

for servo_id in positions:
pose_bone = get_pose_bone_by_servo_id(servo_id, context.scene)
servos[servo_id] = {
"name": pose_bone.bone.name,
"positions": positions[servo_id],
}
try:
indent = int(self.indent)
except ValueError:
indent = None

data = {
"description": 'Blender Servo Animation Positions',
"fps": fps,
"frames": frames,
"seconds": seconds,
"bones": len(positions),
"bones": len(positions[0]),
"armature": context.object.name,
"file": filename,
"scene": context.scene.name,
"servos": servos
"positions": positions
}

content = json.dumps(data, indent=4)
content = json.dumps(data, indent=indent)

with open(filepath, 'w', encoding='utf-8') as file:
file.write(content)
9 changes: 4 additions & 5 deletions addon/ops/servoanim_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class ServoanimExport(Operator, BaseExport, ExportHelper):
bl_idname = "export_anim.servo_positions_servoanim"
bl_label = "Animation Servo Positions (.servoanim)"
bl_idname = "export_anim.servo_animation_servoanim"
bl_label = "Servo Animation (.servoanim)"
bl_description = "Save an SD card optimized file with servo position values of the active armature"

filename_ext = ".servoanim"
Expand All @@ -18,9 +18,8 @@ class ServoanimExport(Operator, BaseExport, ExportHelper):
maxlen=255
)

def export(self, positions, filepath, context):
_fps, frames, _seconds = self.get_time_meta(context.scene)
commands = self.get_commands(frames, positions)
def export(self, positions, filepath, _context):
commands = self.get_commands(positions)

with open(filepath, 'wb') as file:
file.write(bytes(commands))
11 changes: 5 additions & 6 deletions addon/ui/menu_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


class MenuPanel(Panel):
bl_label = "Servo Positions"
bl_label = "Servo Animation"
bl_idname = "TIMELINE_PT_servo"
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
Expand All @@ -27,12 +27,11 @@ def draw(self, context):
else:
self.draw_live_mode_deps(col)

col = layout.column()
col = layout.column(align=True)
col.label(text="Export")
row = col.row(align=True)
row.operator(ArduinoExport.bl_idname, text="Arduino (.h)")
row.operator(JsonExport.bl_idname, text="JSON (.json)")
row.operator(ServoanimExport.bl_idname, text="Servoanim (.servoanim)")
col.operator(ArduinoExport.bl_idname, text="Arduino (.h)")
col.operator(JsonExport.bl_idname, text="JSON (.json)")
col.operator(ServoanimExport.bl_idname, text="Servoanim (.servoanim)")

@classmethod
def draw_live_mode(cls, context, layout, col):
Expand Down
67 changes: 38 additions & 29 deletions addon/utils/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,53 +42,62 @@ def calculate_position(pose_bone):

angle = servo_settings.neutral_angle - rotation_in_degrees
position = round(range_map(angle, 0, servo_settings.rotation_range,
servo_settings.position_min, servo_settings.position_max))
servo_settings.position_min, servo_settings.position_max))

check_min = servo_settings.position_min
check_max = servo_settings.position_max

if position < check_min or position > check_max:
in_range = False
else:
in_range = True
in_range = servo_settings.position_min <= position <= servo_settings.position_max

return position, round(angle, 2), in_range


def calculate_positions(context):
def calculate_positions(context, skip_duplicates):
pose_bones = []
scene = context.scene
positions = []
last_positions = {}
window_manager = context.window_manager
start = scene.frame_start
end = scene.frame_end + 1
start = context.scene.frame_start
end = context.scene.frame_end + 1

positions = {}
window_manager.progress_begin(min=start, max=end)

for pose_bone in context.object.pose.bones:
servo_settings = pose_bone.bone.servo_settings
if servo_settings.active:
pose_bones.append(pose_bone)
positions[servo_settings.servo_id] = []

window_manager.progress_begin(min=start, max=end)

for frame in range(start, end):
scene.frame_set(frame)
frame_positions = calculate_frame_positions(
context, pose_bones, last_positions, skip_duplicates, frame)
positions.append(frame_positions)
window_manager.progress_update(frame)

window_manager.progress_end()

for pose_bone in pose_bones:
bone = pose_bone.bone
position, _angle, in_range = calculate_position(pose_bone)
return positions

if not in_range:
raise RuntimeError(
f"Calculated position {position} for bone {bone.name} "
+ f"is out of range at frame {frame}."
)

positions[bone.servo_settings.servo_id].append(position)
def calculate_frame_positions(context, pose_bones, last_positions, skip_duplicates, frame):
frame_positions = {}

window_manager.progress_update(frame)
context.scene.frame_set(frame)

window_manager.progress_end()
for pose_bone in pose_bones:
bone = pose_bone.bone
servo_id = bone.servo_settings.servo_id
position, _angle, in_range = calculate_position(pose_bone)

return positions
if servo_id not in last_positions:
last_positions[servo_id] = None

if not in_range:
raise RuntimeError(
f"Calculated position {position} for bone {bone.name} "
+ f"is out of range at frame {frame}."
)

if skip_duplicates and last_positions[servo_id] == position:
continue

frame_positions[servo_id] = position
last_positions[servo_id] = position

return frame_positions
Loading

0 comments on commit 39e316b

Please sign in to comment.