예제 #1
0
    def save(self, collection: T.bpy_prop_collection, parent: T.bpy_struct,
             key: str, context: Context):
        """
        Saves this proxy into collection

        Args:
            collection: a collection of datablock references with link/unlink interface
                (e.g a_Collection_instance.objects)
            parent: the structure that contains collection to be loaded (e.g. a Collection instance)
            key: the name of the bpy_collection (e.g "objects")
            context:
        """
        for _, ref_proxy in self._data.items():
            assert isinstance(ref_proxy, DatablockRefProxy)
            datablock = ref_proxy.target(context)
            if datablock:
                collection.link(datablock)
            else:
                logger.info(
                    f"unresolved reference {parent}.{key} -> {ref_proxy.display_string} {ref_proxy.mixer_uuid}"
                )
                add_element = collection.link
                context.proxy_state.unresolved_refs.append(
                    ref_proxy.mixer_uuid, add_element,
                    f"{collection!r}.link({ref_proxy.display_string})")
예제 #2
0
def add_element(collection: T.bpy_prop_collection, proxy: Proxy, index: int,
                context: Context):
    """Add an element to a bpy_prop_collection using the collection specific API.s"""

    if hasattr(collection, "add"):
        # either a bpy_prop_collection  with an rna or a bpy_prop_collection_idprop
        try:
            collection.add()
            return
        except Exception as e:
            logger.error(
                f"add_element: call to add() failed for {context.visit_state.display_path()} ..."
            )
            logger.error(f"... {e!r}")
            raise AddElementFailed from None

    if not hasattr(collection, "bl_rna"):
        # a bpy.types.bpy_prop_collection, e.g Pose.bones
        # We should not even attempt to add elements in these collections since they do not allow it at all.
        # However bpy_prop_collection and collections with an rna both managed by StructCollectionProxy. We need
        # proxy update to update the contents of existing elements, but it should not attempt to add/remove elements.
        # As a consequence, for attributes that fall into this category we trigger updates with additions and
        # deletions that are meaningless. Ignore them.
        # The right design could be to have different proxies for bpy_prop_collection and bpy_struct that behave like
        # collections.
        # see Proxy construction in  read_attribute()
        return
예제 #3
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_sequence(collection: T.bpy_prop_collection, proxy: Proxy,
                          context: Context):
    type_name = proxy.data("type")
    name = proxy.data("name")
    channel = proxy.data("channel")
    frame_start = proxy.data("frame_start")
    if type_name in _effect_sequences:
        # overwritten anyway
        frame_end = frame_start + 1
        return collection.new_effect(name,
                                     type_name,
                                     channel,
                                     frame_start,
                                     frame_end=frame_end)
    if type_name == "SOUND":
        sound = proxy.data("sound")
        target = sound.target(context)
        if not target:
            logger.warning(
                f"missing target ID block for bpy.data.{sound.collection}[{sound.key}] "
            )
            return None
        filepath = target.filepath
        return collection.new_sound(name, filepath, channel, frame_start)
    if type_name == "MOVIE":
        filepath = proxy.data("filepath")
        return collection.new_movie(name, filepath, channel, frame_start)
    if type_name == "IMAGE":
        directory = proxy.data("directory")
        filename = proxy.data("elements").data(0).data("filename")
        filepath = str(Path(directory) / filename)
        return collection.new_image(name, filepath, channel, frame_start)

    logger.warning(f"Sequence type not implemented: {type_name}")
    return None
예제 #4
0
파일: specifics.py 프로젝트: nondejus/mixer
def _truncate_collection_remove(collection: T.bpy_prop_collection, size: int):
    try:
        while len(collection) > size:
            collection.remove(collection[-1])
    except Exception as e:
        logger.error(f"truncate_collection {collection}: exception ...")
        logger.error(f"... {e!r}")
예제 #5
0
    def diff(self, aos: T.bpy_prop_collection, key: str, prop: T.Property,
             context: Context) -> Optional[DeltaUpdate]:
        if len(aos) == 0:
            return None

        array_size, member_type = self.array_attr(aos, self._member_name,
                                                  prop.bl_rna)
        typecode = self._array.typecode
        tmp_array = array.array(typecode,
                                soa_initializer(member_type, array_size))
        if logger.isEnabledFor(logging.DEBUG):
            message = (
                f"diff {aos}.{self._member_name} proxy({len(self._array)} {typecode}) blender'{len(aos)} {member_type}'"
            )
            logger.debug(message)

        try:
            aos.foreach_get(self._member_name, tmp_array)
        except RuntimeError as e:
            logger.error(f"diff soa {aos}.{self._member_name} failed")
            logger.error(
                f"... member size: {len(aos)}, tmp_array: ('{tmp_array.typecode}', {len(tmp_array)})"
            )
            logger.error(f"... exception {e!r}")

        if self._array == tmp_array:
            return None

        diff = self.__class__()
        diff._member_name = self._member_name
        diff._array = tmp_array
        diff._attach(context)
        return DeltaUpdate(diff)
예제 #6
0
파일: specifics.py 프로젝트: nondejus/mixer
def add_element(collection: T.bpy_prop_collection, proxy: Proxy,
                context: Context):
    """Add an element to a bpy_prop_collection using the collection specific API"""
    try:
        collection.add()
    except Exception:
        logger.error(f"add_element: failed for {collection}")
예제 #7
0
    def load(
        self,
        bl_collection: T.bpy_prop_collection,
        key: Union[int, str],
        bl_collection_property: T.Property,
        context: Context,
    ):

        if len(bl_collection) == 0:
            self._data.clear()
            return self

        try:
            context.visit_state.path.append(key)
            # no keys means it is a sequence. However bl_collection.items() returns [(index, item)...]
            is_sequence = not bl_collection.keys()
            if is_sequence:
                # easier for the encoder to always have a dict
                self._data = {
                    MIXER_SEQUENCE: [
                        StructProxy.make(v).load(v, i, context) for i, v in enumerate(bl_collection.values())
                    ]
                }
            else:
                self._data = {k: StructProxy().load(v, k, context) for k, v in bl_collection.items()}
        finally:
            context.visit_state.path.pop()
        return self
예제 #8
0
파일: specifics.py 프로젝트: pypingyi/mixer
def add_element(collection: T.bpy_prop_collection, proxy: Proxy, index: int,
                context: Context):
    """Add an element to a bpy_prop_collection using the collection specific API"""
    try:
        collection.bl_rna
    except AttributeError:
        return

    try:
        collection.add()
    except Exception:
        logger.error(f"add_element: failed for {collection}")
예제 #9
0
파일: specifics.py 프로젝트: pypingyi/mixer
def remove_datablock(collection: T.bpy_prop_collection, datablock: T.ID):
    """Delete a datablock from its bpy.data collection"""
    if isinstance(datablock, T.Scene):
        from mixer.blender_client.scene import delete_scene

        delete_scene(datablock)
    elif isinstance(datablock, T.Key):
        # the doc labels it unsafe, use sparingly
        bpy.data.batch_remove([datablock])
    elif isinstance(datablock, T.Library):
        # TODO 2.91 has BlendDatalibraries.remove()
        logger.warning(f"remove_datablock({datablock}): ignored (library)")
    else:
        collection.remove(datablock)
예제 #10
0
파일: specifics.py 프로젝트: nondejus/mixer
def add_datablock_ref_element(collection: T.bpy_prop_collection,
                              datablock: T.ID):
    """Add an element to a bpy_prop_collection using the collection specific API"""
    bl_rna = getattr(collection, "bl_rna", None)
    if bl_rna is not None:
        if isinstance(bl_rna, _link_collections):
            collection.link(datablock)
            return

        if isinstance(bl_rna, type(T.IDMaterials.bl_rna)):
            collection.append(datablock)
            return

    logging.warning(
        f"add_datablock_ref_element : no implementation for {collection} ")
예제 #11
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_default(collection: T.bpy_prop_collection, proxy: Proxy,
                         context: Context):
    try:
        return collection.add()
    except Exception:
        pass

    # try our best
    new_or_add = getattr(collection, "new", None)
    if new_or_add is None:
        new_or_add = getattr(collection, "add", None)
    if new_or_add is None:
        logger.warning(f"Not implemented new or add for {collection} ...")
        return None

    try:
        return new_or_add()
    except TypeError:
        try:
            key = proxy.data("name")
            return new_or_add(key)
        except Exception:
            logger.warning(
                f"Not implemented new or add for type {type(collection)} for {collection}[{key}] ..."
            )
            for s in traceback.format_exc().splitlines():
                logger.warning(f"...{s}")
    return None
예제 #12
0
    def save_array(self, aos: T.bpy_prop_collection, member_name, array_: array.array):
        if logger.isEnabledFor(logging.DEBUG):
            message = f"save_array {aos}.{member_name}"
            if self._array is not None:
                message += f" proxy ({len(self._array)} {self._array.typecode})"
            message += f" incoming ({len(array_)} {array_.typecode})"
            message += f" blender_length ({len(aos)})"
            logger.debug(message)

        self._array = array_
        try:
            aos.foreach_set(member_name, array_)
        except RuntimeError as e:
            logger.error(f"saving soa {aos}.{member_name} failed")
            logger.error(f"... member size: {len(aos)}, array: ('{array_.typecode}', {len(array_)})")
            logger.error(f"... exception {e!r}")
예제 #13
0
def truncate_collection(target: T.bpy_prop_collection,
                        incoming_keys: List[str]):
    if not hasattr(target, "bl_rna"):
        return

    target_rna = target.bl_rna
    if any(isinstance(target_rna, t) for t in always_clear):
        target.clear()
        return

    incoming_keys = set(incoming_keys)
    existing_keys = set(target.keys())
    truncate_keys = existing_keys - incoming_keys
    if not truncate_keys:
        return
    if isinstance(target_rna, type(T.KeyingSets.bl_rna)):
        for k in truncate_keys:
            target.active_index = target.find(k)
            bpy.ops.anim.keying_set_remove()
    else:
        try:
            for k in truncate_keys:
                target.remove(target[k])
        except Exception:
            logger.warning(
                f"Not implemented truncate_collection for type {target.bl_rna} for {target} ..."
            )
            for s in traceback.format_exc().splitlines():
                logger.warning(f"...{s}")
예제 #14
0
    def diff(self, collection: T.bpy_prop_collection, key: str,
             collection_property: T.Property,
             context: Context) -> Optional[DeltaUpdate]:
        """
        Computes the difference between the state of an item tracked by this proxy and its Blender state.

        As this proxy tracks a collection, the result will be a DeltaUpdate that contains a DatablockCollectionProxy
        with an Delta item per added, deleted or update item

        Args:
            collection: the collection diff against this proxy
            collection_property: the property of collection in its enclosing object
        """

        # This method is called from the depsgraph handler. The proxy holds a representation of the Blender state
        # before the modification being processed. So the changeset is (Blender state - proxy state)

        # TODO how can this replace BpyBlendDiff ?

        diff = self.__class__()

        item_property = collection_property.fixed_type

        # keys are uuids
        # BpyDataCollectionDiff.diff() for why proxies without datablocks are ignores
        proxy_keys = {k for k, v in self._data.items() if v.target(context)}

        blender_items = {
            datablock.mixer_uuid: datablock
            for datablock in collection.values()
        }
        blender_keys = blender_items.keys()
        added_keys = blender_keys - proxy_keys
        deleted_keys = proxy_keys - blender_keys
        maybe_updated_keys = proxy_keys & blender_keys

        for k in added_keys:
            value = read_attribute(blender_items[k], k, item_property,
                                   collection, context)
            assert isinstance(value, (DatablockProxy, DatablockRefProxy))
            diff._data[k] = DeltaAddition(value)

        for k in deleted_keys:
            diff._data[k] = DeltaDeletion(self._data[k])

        for k in maybe_updated_keys:
            delta = diff_attribute(blender_items[k], k, item_property,
                                   self.data(k), context)
            if delta is not None:
                assert isinstance(delta, DeltaUpdate)
                diff._data[k] = delta

        if len(diff._data):
            return DeltaUpdate(diff)

        return None
예제 #15
0
def _(collection: T.bpy_prop_collection, proxy: Proxy, index: int,
      context: Context) -> T.bpy_struct:
    node = context.visit_state.attribute(-1)
    if not isinstance(node, _node_groups):
        logger.warning(
            f"Unexpected add node input for {node} at {context.visit_path.path()}"
        )

    socket_type = proxy.data("bl_idname")
    name = proxy.data("name")
    return collection.new(socket_type, name)
예제 #16
0
파일: specifics.py 프로젝트: pypingyi/mixer
def _(collection: T.bpy_prop_collection, proxy: Proxy, index: int,
      context: Context) -> T.bpy_struct:
    node_type = proxy.data("bl_idname")
    try:
        return collection.new(node_type)
    except RuntimeError as e:
        name = proxy.data("name")
        logger.error(
            f"add_element failed for node {name!r} into {context.visit_state.display_path()} ..."
        )
        logger.error(f"... {e!r}")
        raise AddElementFailed from None
예제 #17
0
파일: specifics.py 프로젝트: nondejus/mixer
def fit_aos(target: T.bpy_prop_collection, proxy: AosProxy, context: Context):
    """
    Adjust the size of a bpy_prop_collection proxified as an array of structures (e.g. MeshVertices)
    """

    if not hasattr(target, "bl_rna"):
        return

    target_rna = target.bl_rna
    if isinstance(target_rna, _resize_geometry_types):
        existing_length = len(target)
        incoming_length = proxy.length
        if existing_length != incoming_length:
            if existing_length != 0:
                logger.error(f"resize_geometry(): size mismatch for {target}")
                logger.error(
                    f"... existing: {existing_length} incoming {incoming_length}"
                )
                return
            logger.debug(
                f"resizing geometry: add({incoming_length}) for {target}")
            target.add(incoming_length)
        return

    if isinstance(target_rna, type(T.GPencilStrokePoints.bl_rna)):
        existing_length = len(target)
        incoming_length = proxy.length
        delta = incoming_length - existing_length
        if delta > 0:
            target.add(delta)
        else:
            while delta < 0:
                target.pop()
                delta += 1
        return

    if isinstance(target_rna, type(T.SplineBezierPoints.bl_rna)):
        existing_length = len(target)
        incoming_length = proxy.length
        delta = incoming_length - existing_length
        if delta > 0:
            target.add(delta)
        else:
            logger.error("Remove not implemented for type SplineBezierPoints")
        return

    logger.error(
        f"Not implemented fit_aos for type {target.bl_rna} for {target} ...")
예제 #18
0
    def diff(self, aos: T.bpy_prop_collection, key: str, prop: T.Property, context: Context) -> Optional[DeltaUpdate]:
        if len(aos) == 0:
            return None

        array_size, member_type = self.array_attr(aos, self._member_name, prop.bl_rna)
        typecode = self._array.typecode
        tmp_array = array.array(typecode, soa_initializer(member_type, array_size))
        if logger.isEnabledFor(logging.DEBUG):
            message = (
                f"diff {aos}.{self._member_name} proxy({len(self._array)} {typecode}) blender'{len(aos)} {member_type}'"
            )
            logger.debug(message)
        aos.foreach_get(self._member_name, tmp_array)

        force_diff = context.visit_state.scratchpad.get("force_soa_diff", False)
        if self._array == tmp_array and not force_diff:
            return None

        diff = self.__class__()
        diff._member_name = self._member_name
        diff._array = tmp_array
        diff._attach(context)
        return DeltaUpdate(diff)
 def load(
     self,
     bl_collection: T.bpy_prop_collection,
     context: Context,
 ):
     self._sequence.clear()
     for i, v in enumerate(bl_collection.values()):
         context.visit_state.push(v, i)
         try:
             self._sequence.append(_proxy_factory(v).load(v, context))
         except Exception as e:
             logger.error(f"Exception during load at {context.visit_state.display_path()} ...")
             logger.error(f"... {e!r}")
         finally:
             context.visit_state.pop()
     return self
예제 #20
0
    def load(
        self,
        bl_collection: T.bpy_prop_collection,
        key: Union[int, str],
        bl_collection_property: T.Property,
        context: Context,
    ):

        context.visit_state.path.append(key)
        try:
            self._sequence = [
                _proxy_factory(v).load(v, i, context)
                for i, v in enumerate(bl_collection.values())
            ]
        finally:
            context.visit_state.path.pop()
        return self
예제 #21
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_keyingset(collection: T.bpy_prop_collection, proxy: Proxy,
                           context: Context):
    # TODO current implementation fails
    # All keying sets paths have an empty name, and insertion with add() fails
    # with an empty name
    target_ref = proxy.data("id")
    if target_ref is None:
        target = None
    else:
        target = target_ref.target(context)
    data_path = proxy.data("data_path")
    index = proxy.data("array_index")
    group_method = proxy.data("group_method")
    group_name = proxy.data("group")
    return collection.add(target_id=target,
                          data_path=data_path,
                          index=index,
                          group_method=group_method,
                          group_name=group_name)
예제 #22
0
def add_element(proxy: Proxy, collection: T.bpy_prop_collection, key: str):
    """Add an element to a bpy_prop_collection using the collection specific API
    """

    bl_rna = getattr(collection, "bl_rna", None)
    if bl_rna is not None:
        if isinstance(bl_rna, type(T.KeyingSets.bl_rna)):
            idname = proxy.data("bl_idname")
            return collection.new(name=key, idname=idname)

        if isinstance(bl_rna, type(T.KeyingSetPaths.bl_rna)):
            # TODO current implementation fails
            # All keying sets paths have an empty name, and insertion with add()à failes
            # with an empty name
            target_ref = proxy.data("id")
            if target_ref is None:
                target = None
            else:
                target = target_ref.target()
            data_path = proxy.data("data_path")
            index = proxy.data("array_index")
            group_method = proxy.data("group_method")
            group_name = proxy.data("group")
            return collection.add(target_id=target,
                                  data_path=data_path,
                                  index=index,
                                  group_method=group_method,
                                  group_name=group_name)

        if isinstance(bl_rna, type(T.Nodes.bl_rna)):
            node_type = proxy.data("bl_idname")
            return collection.new(node_type)

        if isinstance(bl_rna, type(T.Sequences.bl_rna)):
            type_ = proxy.data("type")
            name = proxy.data("name")
            channel = proxy.data("channel")
            frame_start = proxy.data("frame_start")
            if type_ in effect_sequences:
                # overwritten anyway
                frame_end = frame_start + 1
                return collection.new_effect(name,
                                             type_,
                                             channel,
                                             frame_start,
                                             frame_end=frame_end)
            if type_ == "SOUND":
                sound = proxy.data("sound")
                target = sound.target()
                if not target:
                    logger.warning(
                        f"missing target ID block for bpy.data.{sound.collection}[{sound.key}] "
                    )
                    return None
                filepath = target.filepath
                return collection.new_sound(name, filepath, channel,
                                            frame_start)
            if type_ == "MOVIE":
                filepath = proxy.data("filepath")
                return collection.new_movie(name, filepath, channel,
                                            frame_start)
            if type_ == "IMAGE":
                directory = proxy.data("directory")
                filename = proxy.data("elements").data(0).data("filename")
                filepath = str(Path(directory) / filename)
                return collection.new_image(name, filepath, channel,
                                            frame_start)

            logger.warning(f"Sequence type not implemented: {type_}")
            # SCENE may be harder than it seems, since we cannot order scene creations.
            # Currently the creation order is the "deepmost" order as listed in proxy.py:_creation_order
            # but it does not work for this case
            return None

        if isinstance(bl_rna, type(T.SequenceModifiers.bl_rna)):
            name = proxy.data("name")
            type_ = proxy.data("type")
            return collection.new(name, type_)

    try:
        return collection.add()
    except Exception:
        pass

    # try our best
    new_or_add = getattr(collection, "new", None)
    if new_or_add is None:
        new_or_add = getattr(collection, "add", None)
    if new_or_add is None:
        logger.warning(
            f"Not implemented new or add for bpy.data.{collection}[{key}] ...")
        return None
    try:
        return new_or_add(key)
    except Exception:
        logger.warning(
            f"Not implemented new or add for type {type(collection)} for {collection}[{key}] ..."
        )
        for s in traceback.format_exc().splitlines():
            logger.warning(f"...{s}")
        return None
예제 #23
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_location(collection: T.bpy_prop_collection, proxy: Proxy,
                          context: Context):
    location = proxy.data("location")
    return collection.new(location[0], location[1])
예제 #24
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_name_eq(collection: T.bpy_prop_collection, proxy: Proxy,
                         context: Context):
    name = proxy.data("name")
    return collection.new(name=name)
예제 #25
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_idname(collection: T.bpy_prop_collection, proxy: Proxy,
                        context: Context):
    node_type = proxy.data("bl_idname")
    return collection.new(node_type)
예제 #26
0
파일: specifics.py 프로젝트: nondejus/mixer
def _truncate_collection_clear(collection: T.bpy_prop_collection, size: int):
    collection.clear()
예제 #27
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_info(collection: T.bpy_prop_collection, proxy: Proxy,
                      context: Context):
    name = proxy.data("info")
    return collection.new(name)
예제 #28
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_frame_number(collection: T.bpy_prop_collection, proxy: Proxy,
                              context: Context):
    frame_number = proxy.data("frame_number")
    return collection.new(frame_number)
예제 #29
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_bl_label(collection: T.bpy_prop_collection, proxy: Proxy,
                          context: Context):
    label = proxy.data("bl_label")
    idname = proxy.data("bl_idname")
    return collection.new(name=label, idname=idname)
예제 #30
0
파일: specifics.py 프로젝트: nondejus/mixer
def _add_element_type_eq(collection: T.bpy_prop_collection, proxy: Proxy,
                         context: Context):
    type_ = proxy.data("type")
    return collection.new(type=type_)