Skip to content

Commit 4a3ec65

Browse files
Maya Tool - Load Markers - Support World "Bundle Space".
Load the new v5 uvtrack format, for 2D (also with 3D) points. Uses the "Bundle Space" user preference option to default the Load Marker UI's widget values. Adds tests and test files for new version 5 (v5) uvtrack format. GitHub issue #275.
1 parent e0935e3 commit 4a3ec65

File tree

11 files changed

+635
-80
lines changed

11 files changed

+635
-80
lines changed

python/mmSolver/tools/loadmarker/constant.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2018 David Cattermole.
1+
# Copyright (C) 2018, 2025 David Cattermole.
22
#
33
# This file is part of mmSolver.
44
#
@@ -33,6 +33,7 @@
3333
UV_TRACK_FORMAT_VERSION_2 = 2
3434
UV_TRACK_FORMAT_VERSION_3 = 3
3535
UV_TRACK_FORMAT_VERSION_4 = 4
36+
UV_TRACK_FORMAT_VERSION_5 = 5
3637

3738
UV_TRACK_HEADER_VERSION_2 = {
3839
'version': UV_TRACK_FORMAT_VERSION_2,
@@ -46,6 +47,10 @@
4647
'version': UV_TRACK_FORMAT_VERSION_4,
4748
}
4849

50+
UV_TRACK_HEADER_VERSION_5 = {
51+
'version': UV_TRACK_FORMAT_VERSION_5,
52+
}
53+
4954

5055
# UI values
5156
LOAD_MODE_NEW_VALUE = 'Create New Markers'
@@ -75,5 +80,6 @@
7580
CONFIG_PATH_USE_OVERSCAN = 'data/use_overscan'
7681
CONFIG_PATH_DISTORTION_MODE = 'data/distortion_mode'
7782
CONFIG_PATH_LOAD_BUNDLE_POSITION = 'data/load_bundle_position'
83+
CONFIG_PATH_BUNDLE_SPACE = 'data/bundle_space'
7884
CONFIG_PATH_RENAME_MARKERS = 'data/rename_markers'
7985
CONFIG_PATH_RENAME_MARKERS_NAME = 'data/rename_markers_name'

python/mmSolver/tools/loadmarker/lib/mayareadfile.py

Lines changed: 188 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2018, 2019, 2020 David Cattermole.
1+
# Copyright (C) 2018, 2019, 2020, 2025 David Cattermole.
22
#
33
# This file is part of mmSolver.
44
#
@@ -217,7 +217,85 @@ def __set_attr_keyframes(
217217
return anim_fn
218218

219219

220-
def __set_node_data(mkr, bnd, mkr_data, load_bnd_pos, overscan_x, overscan_y):
220+
def __set_node_data_bundle_local(mkr_data, bnd_node):
221+
bnd_x = mkr_data.get_bundle_x()
222+
bnd_y = mkr_data.get_bundle_y()
223+
bnd_z = mkr_data.get_bundle_z()
224+
bnd_lock_x = mkr_data.get_bundle_lock_x()
225+
bnd_lock_y = mkr_data.get_bundle_lock_y()
226+
bnd_lock_z = mkr_data.get_bundle_lock_z()
227+
228+
maya.cmds.setAttr(bnd_node + '.translateX', lock=False)
229+
maya.cmds.setAttr(bnd_node + '.translateY', lock=False)
230+
maya.cmds.setAttr(bnd_node + '.translateZ', lock=False)
231+
232+
if isinstance(bnd_x, float):
233+
maya.cmds.setAttr(bnd_node + '.translateX', bnd_x)
234+
if isinstance(bnd_y, float):
235+
maya.cmds.setAttr(bnd_node + '.translateY', bnd_y)
236+
if isinstance(bnd_z, float):
237+
maya.cmds.setAttr(bnd_node + '.translateZ', bnd_z)
238+
239+
if isinstance(bnd_lock_x, bool) and bnd_lock_x:
240+
maya.cmds.setAttr(bnd_node + '.translateX', lock=True)
241+
if isinstance(bnd_lock_y, bool) and bnd_lock_y:
242+
maya.cmds.setAttr(bnd_node + '.translateY', lock=True)
243+
if isinstance(bnd_lock_z, bool) and bnd_lock_z:
244+
maya.cmds.setAttr(bnd_node + '.translateZ', lock=True)
245+
246+
return
247+
248+
249+
def __set_node_data_bundle_world(mkr_data, bnd_node):
250+
bnd_world_x = mkr_data.get_bundle_world_x()
251+
bnd_world_y = mkr_data.get_bundle_world_y()
252+
bnd_world_z = mkr_data.get_bundle_world_z()
253+
254+
has_keyframe_data = isinstance(bnd_world_x, keyframedata.KeyframeData)
255+
256+
bnd_lock_x = mkr_data.get_bundle_lock_x()
257+
bnd_lock_y = mkr_data.get_bundle_lock_y()
258+
bnd_lock_z = mkr_data.get_bundle_lock_z()
259+
bnd_lock_xyz = bnd_lock_x or bnd_lock_y or bnd_lock_z
260+
261+
maya.cmds.setAttr(bnd_node + '.translateX', lock=False)
262+
maya.cmds.setAttr(bnd_node + '.translateY', lock=False)
263+
maya.cmds.setAttr(bnd_node + '.translateZ', lock=False)
264+
265+
if has_keyframe_data is True:
266+
assert isinstance(bnd_world_x, keyframedata.KeyframeData)
267+
assert isinstance(bnd_world_y, keyframedata.KeyframeData)
268+
assert isinstance(bnd_world_z, keyframedata.KeyframeData)
269+
assert bnd_world_x.get_length() > 0
270+
assert bnd_world_y.get_length() > 0
271+
assert bnd_world_z.get_length() > 0
272+
__set_attr_keyframes(bnd_node, 'translateX', bnd_world_x)
273+
__set_attr_keyframes(bnd_node, 'translateY', bnd_world_y)
274+
__set_attr_keyframes(bnd_node, 'translateZ', bnd_world_z)
275+
else:
276+
assert isinstance(bnd_world_x, float)
277+
assert isinstance(bnd_world_y, float)
278+
assert isinstance(bnd_world_z, float)
279+
maya.cmds.setAttr(bnd_node + '.translateX', bnd_world_x)
280+
maya.cmds.setAttr(bnd_node + '.translateY', bnd_world_y)
281+
maya.cmds.setAttr(bnd_node + '.translateZ', bnd_world_z)
282+
283+
if bnd_lock_xyz is True:
284+
maya.cmds.setAttr(bnd_node + '.translateX', lock=True)
285+
maya.cmds.setAttr(bnd_node + '.translateY', lock=True)
286+
maya.cmds.setAttr(bnd_node + '.translateZ', lock=True)
287+
return
288+
289+
290+
def __set_node_data(
291+
mkr,
292+
bnd,
293+
mkr_data,
294+
load_bnd_pos,
295+
world_space_bnd_pos,
296+
overscan_x,
297+
overscan_y,
298+
):
221299
"""
222300
Set and override the data on the given marker node.
223301
@@ -235,6 +313,9 @@ def __set_node_data(mkr, bnd, mkr_data, load_bnd_pos, overscan_x, overscan_y):
235313
:param load_bnd_pos: Should we set Bundle positions?
236314
:type load_bnd_pos: bool
237315
316+
:param world_space_bnd_pos: Bundle positions should be in world-space.
317+
:type world_space_bnd_pos: bool
318+
238319
:param overscan_x: Overscan factor to apply to the MarkerData x values.
239320
:type overscan_x: float
240321
@@ -248,6 +329,7 @@ def __set_node_data(mkr, bnd, mkr_data, load_bnd_pos, overscan_x, overscan_y):
248329
assert bnd is None or isinstance(bnd, mmapi.Bundle)
249330
assert isinstance(mkr_data, markerdata.MarkerData)
250331
assert load_bnd_pos is None or isinstance(load_bnd_pos, bool)
332+
assert isinstance(world_space_bnd_pos, bool)
251333
assert isinstance(overscan_x, float)
252334
assert isinstance(overscan_y, float)
253335
mkr_node = mkr.get_node()
@@ -307,30 +389,43 @@ def __set_node_data(mkr, bnd, mkr_data, load_bnd_pos, overscan_x, overscan_y):
307389
# Set Bundle Position
308390
if bnd and load_bnd_pos:
309391
bnd_node = bnd.get_node()
310-
bnd_x = mkr_data.get_bundle_x()
311-
bnd_y = mkr_data.get_bundle_y()
312-
bnd_z = mkr_data.get_bundle_z()
313-
bnd_lock_x = mkr_data.get_bundle_lock_x()
314-
bnd_lock_y = mkr_data.get_bundle_lock_y()
315-
bnd_lock_z = mkr_data.get_bundle_lock_z()
316-
317-
maya.cmds.setAttr(bnd_node + '.translateX', lock=False)
318-
maya.cmds.setAttr(bnd_node + '.translateY', lock=False)
319-
maya.cmds.setAttr(bnd_node + '.translateZ', lock=False)
320-
321-
if isinstance(bnd_x, float):
322-
maya.cmds.setAttr(bnd_node + '.translateX', bnd_x)
323-
if isinstance(bnd_y, float):
324-
maya.cmds.setAttr(bnd_node + '.translateY', bnd_y)
325-
if isinstance(bnd_z, float):
326-
maya.cmds.setAttr(bnd_node + '.translateZ', bnd_z)
327-
328-
if isinstance(bnd_lock_x, bool):
329-
maya.cmds.setAttr(bnd_node + '.translateX', lock=True)
330-
if isinstance(bnd_lock_y, bool):
331-
maya.cmds.setAttr(bnd_node + '.translateY', lock=True)
332-
if isinstance(bnd_lock_z, bool):
333-
maya.cmds.setAttr(bnd_node + '.translateZ', lock=True)
392+
393+
if world_space_bnd_pos is True:
394+
is_world_static = (
395+
isinstance(mkr_data.bundle_world_x, float)
396+
and isinstance(mkr_data.bundle_world_y, float)
397+
and isinstance(mkr_data.bundle_world_z, float)
398+
)
399+
400+
has_world_xyz = False
401+
if is_world_static:
402+
has_world_xyz = True
403+
else:
404+
world_x_count = mkr_data.bundle_world_x.get_length()
405+
world_y_count = mkr_data.bundle_world_y.get_length()
406+
world_z_count = mkr_data.bundle_world_z.get_length()
407+
has_world_xyz = (
408+
world_x_count > 0
409+
and world_x_count == world_y_count == world_z_count
410+
)
411+
412+
if has_world_xyz:
413+
__set_node_data_bundle_world(mkr_data, bnd_node)
414+
else:
415+
# We were asked to give bundle positions in
416+
# world-space, but we cannot do that. Warning the user
417+
# seems appropriate.
418+
msg = (
419+
'%r | %r; '
420+
'World-space bundle data does not exist, '
421+
'cannot set world-space position.'
422+
)
423+
LOG.warning(msg, mkr_name, bnd_node)
424+
425+
__set_node_data_bundle_local(mkr_data, bnd_node)
426+
else:
427+
__set_node_data_bundle_local(mkr_data, bnd_node)
428+
334429
return mkr, bnd
335430

336431

@@ -341,6 +436,7 @@ def create_nodes(
341436
col=None,
342437
with_bundles=None,
343438
load_bundle_position=None,
439+
world_space_bundle_position=None,
344440
camera_field_of_view=None,
345441
):
346442
"""
@@ -366,22 +462,32 @@ def create_nodes(
366462
:param load_bundle_position: Apply the 3D positions to bundle.
367463
:type load_bundle_position: bool
368464
465+
:param world_space_bundle_position: Set 3D bundle positions in world space?
466+
:type world_space_bundle_position: bool
467+
369468
:param camera_field_of_view: The camera field of view of the
370469
original camera with this 2D data.
371470
:type camera_field_of_view: [(int, float, float)]
372471
373472
:returns: List of Markers.
374473
:rtype: [Marker, ..]
375474
"""
376-
if with_bundles is None:
377-
with_bundles = True
378-
if load_bundle_position is None:
379-
load_bundle_position = True
380475
assert isinstance(cam, mmapi.Camera)
381476
assert isinstance(mkr_grp, mmapi.MarkerGroup)
382477
assert col is None or isinstance(col, mmapi.Collection)
478+
479+
if with_bundles is None:
480+
with_bundles = True
383481
assert isinstance(with_bundles, bool)
482+
483+
if load_bundle_position is None:
484+
load_bundle_position = True
384485
assert isinstance(load_bundle_position, bool)
486+
487+
if world_space_bundle_position is None:
488+
world_space_bundle_position = False
489+
assert isinstance(world_space_bundle_position, bool)
490+
385491
assert camera_field_of_view is None or isinstance(
386492
camera_field_of_view, (list, tuple)
387493
)
@@ -413,7 +519,13 @@ def create_nodes(
413519
if mkr is not None:
414520
# Set attributes and add into list
415521
__set_node_data(
416-
mkr, bnd, mkr_data, load_bundle_position, overscan_x, overscan_y
522+
mkr,
523+
bnd,
524+
mkr_data,
525+
load_bundle_position,
526+
world_space_bundle_position,
527+
overscan_x,
528+
overscan_y,
417529
)
418530
mkr_list.append(mkr)
419531

@@ -503,19 +615,39 @@ def _find_marker_data(mkr, mkr_data_list):
503615
return found_mkr_data
504616

505617

506-
def _update_node(mkr, bnd, mkr_data, load_bundle_position, overscan_x, overscan_y):
618+
def _update_node(
619+
mkr,
620+
bnd,
621+
mkr_data,
622+
load_bundle_position,
623+
world_space_bundle_position,
624+
overscan_x,
625+
overscan_y,
626+
):
507627
"""
508628
Set the MarkerData on the given Marker and Bundle.
509629
"""
510630
assert isinstance(mkr, mmapi.Marker)
511631
assert bnd is None or isinstance(bnd, mmapi.Bundle)
512632
assert isinstance(mkr_data, markerdata.MarkerData)
513-
__set_node_data(mkr, bnd, mkr_data, load_bundle_position, overscan_x, overscan_y)
633+
__set_node_data(
634+
mkr,
635+
bnd,
636+
mkr_data,
637+
load_bundle_position,
638+
world_space_bundle_position,
639+
overscan_x,
640+
overscan_y,
641+
)
514642
return
515643

516644

517645
def update_nodes(
518-
mkr_list, mkr_data_list, load_bundle_position=None, camera_field_of_view=None
646+
mkr_list,
647+
mkr_data_list,
648+
load_bundle_position=None,
649+
world_space_bundle_position=None,
650+
camera_field_of_view=None,
519651
):
520652
"""
521653
Update the given mkr_list with data from mkr_data_list.
@@ -530,6 +662,9 @@ def update_nodes(
530662
:param load_bundle_position: Apply the 3D positions to bundle.
531663
:type load_bundle_position: bool
532664
665+
:param world_space_bundle_position: Set 3D bundle positions in world space?
666+
:type world_space_bundle_position: bool
667+
533668
:param camera_field_of_view: The camera field of view of the
534669
original camera with this 2D data.
535670
:type camera_field_of_view: [(int, float, float)]
@@ -539,9 +674,12 @@ def update_nodes(
539674
"""
540675
if load_bundle_position is None:
541676
load_bundle_position = True
677+
if world_space_bundle_position is None:
678+
world_space_bundle_position = False
542679
assert isinstance(mkr_list, (list, tuple, set))
543680
assert isinstance(mkr_data_list, (list, tuple, set))
544681
assert isinstance(load_bundle_position, bool)
682+
assert isinstance(world_space_bundle_position, bool)
545683
assert camera_field_of_view is None or isinstance(
546684
camera_field_of_view, (list, tuple)
547685
)
@@ -570,7 +708,15 @@ def update_nodes(
570708
cam_shp = cam.get_shape_node()
571709
fallback_overscan = (1.0, 1.0)
572710
overscan_x, overscan_y = overscan_per_camera.get(cam_shp, fallback_overscan)
573-
_update_node(mkr, bnd, mkr_data, load_bundle_position, overscan_x, overscan_y)
711+
_update_node(
712+
mkr,
713+
bnd,
714+
mkr_data,
715+
load_bundle_position,
716+
world_space_bundle_position,
717+
overscan_x,
718+
overscan_y,
719+
)
574720
else:
575721
# Make a copy of mkr_list and mkr_data_list, to avoid any
576722
# possibility of the given arguments mkr_list and mkr_data_list
@@ -591,7 +737,13 @@ def update_nodes(
591737
fallback_overscan,
592738
)
593739
_update_node(
594-
mkr, bnd, mkr_data, load_bundle_position, overscan_x, overscan_y
740+
mkr,
741+
bnd,
742+
mkr_data,
743+
load_bundle_position,
744+
world_space_bundle_position,
745+
overscan_x,
746+
overscan_y,
595747
)
596748
mkr_data_list.remove(mkr_data)
597749
mkr_list_changed.append(mkr)

0 commit comments

Comments
 (0)