How do you seamlessly weight-paint two separate objects in blender?

I’ve struggled to figure out a good way to do this for a long time now, say you have this case on an organic model and let us pretend this is an arm:


Here the forearm is a separate object, perhaps it can be asset-swapped or whatever the case may be. What’s an easy way to continue weight-painting so it will deform as if it was a unified object? It’s possible to merge the two objects and separate them after doing the weights, but this isn’t perfect either because that forearm might have modifier or other sensitive data that would be lost upon joining it with the upper arm portion.

This is a bit challenging problem. Once I struggled a lot to find out how to solve a Separated Head from the Body (the Neck was cut off, near the Head Joint).

First: The Weight Paint affecting both Meshes, must be exact. It would be as if one were Weight Painting Ratios on a single Mesh Object.
Thus, if one has got a Head Bone and a Neck Bone, the same Bones, for sure, can be reused for the Weight Paint on both Mesh Objects, but the Blended Weights (Ratios) must be exactly the same on both Meshes, in respect to the Deform Bones which are affecting both Meshes.
Note: there would be different approach to reach the exactitude here. If there are very few Vertices, one easy approach is just to set manually one by one, matching values gradually.

Second: It’s probably necessary that many impactful Modifiers must also be equal, for both Meshes. Something is very likely to go wrong, if there are any substantial difference between, from one Mesh Object to the other, let’s say, their Armature Modifiers (e.g., one has got Preserve Volume Option on, the other off), or their Subdivision Surface Modifiers (e.g., different Levels of auto-Subdivisions).

Third: if the Mesh is Low-Poly, or very Low-Poly, the Meshes in the connecting Edge Ring won’t look seaminless. To my experience. Thus, more real Subdivisions (on Edit Mode for both Mesh Objects), a higher Mesh Density there therefore, is usually a good idea. I just don’t remember if only a few extra Transversal Edge Rings (wristwatch-like, bracers-like circular orientation around the Limb) will do closest to the Connecting Region, or only a few extra Aligned Loop Cuts with the Limb direction will do, or, it would be needed to cross both Transversal and Aligned Loop Cuts in order to suppress Low-Poly bad visual seaming in Render Preview (I believe it’s the first case). It is likely, a few of Subdivisions alone from Subdivision Surface Modifier should also be an alternative, for the whole Mesh Objects, since it’s a visual problem of Low-Poly lacking Subdivisions glitch, it’s not a structural thing (I believe). However, with the real Subdivisions on a very specific Mesh Region, it’s way more economic than having to give Modifie Subdivisions to the whole Meshes all at once (it’s a waste; unless it would be required for solving other problems of the Model, and simultaneously it solves the Connecting Region glitch problem).

Fourth: Do make sure the feature of Shade Smooth or Auto Smooth, is the same for both Mesh Objects. The former may be accessed from RMB-Click on a Mesh Object on Object Mode, and the latter from the Properties Editor. If Auto-Smooth is being used… it should be safe if its settings are the same for both Mesh Objects… but can never be sure; if it doesn’t work, try pure Smooth for both.

I threw something together as part of a bigger add-on. it makes duplicates of the original objects, applies their modifiers and joins them. the original objects then receives data-transfer modifiers targeting the new joined object. it transfers the weights based on nearest vertices. so i would paint on the joined object, and when finished those data-transfer modifiers would be applied on the original objects. if the form of the original mesh changes, it would make a new joined object and transfer the weights from the old joined object to the new one.

it has its own issues probably but it seems to work in the specific case i’m on, will make it more general after i’ve tested it more.

class REDTHREAD_OT_CreateWeightObject(bpy.types.Operator):
    bl_label = "create_weight_object"
    bl_idname = "redthread.create_weight_object"
    bl_options = {'UNDO'}

    def execute(self, context):
        asset = get_active_asset()
        
        riging_col = asset.rigging_col
        
        all_objects = []
        
        for part in asset.parts:
            col = part.lowpoly_col
            for obj in col.all_objects:
                all_objects.append(obj)
                
                
        linked_objects = []
        for obj in all_objects:
            for col in obj.users_collection:
                if col == part.lowpoly_model_col and obj != 'EMPTY' and obj.redthread.workflow == 'LOWPOLY':
                    linked_objects.append(obj)


        copies = AddonOperations.make_copies(linked_objects, asset.rigging_col)
        weight_ob = AddonOperations.join_objects(context, copies)
        
        weight_ob.redthread.workflow = 'WEIGHTS'
        
        if asset.weight_object is not None:
            old_weight_ob = asset.weight_object
            new_weight_obj_weight_transfer_mod = weight_ob.modifiers.new('REDTHREAD_TRANSFER_WEIGHTS', 'DATA_TRANSFER')
            new_weight_obj_weight_transfer_mod.use_vert_data = True
            new_weight_obj_weight_transfer_mod.data_types_verts = {'VGROUP_WEIGHTS'}
            new_weight_obj_weight_transfer_mod.object = old_weight_ob
            set_active(weight_ob)
            bpy.ops.object.modifier_apply(modifier="REDTHREAD_TRANSFER_WEIGHTS")
            free_object(old_weight_ob)

        asset.weight_object = weight_ob
        
        weight_arma_mod = weight_ob.modifiers.new('REDTHREAD_ARMATURE', 'ARMATURE')
        if asset.armature_object is not None:
            weight_arma_mod.object = asset.armature_object

        for obj in linked_objects:
            arma_mod = None
            weight_transfer_mod = None
            for modifier in obj.modifiers:
                if modifier.type == 'ARMATURE':
                    arma_mod = modifier
                if modifier.name == 'REDTHREAD_TRANSFER_WEIGHTS':
                    weight_transfer_mod = modifier


            if weight_transfer_mod is None:
                weight_transfer_mod = obj.modifiers.new('REDTHREAD_TRANSFER_WEIGHTS', 'DATA_TRANSFER')
                weight_transfer_mod.use_vert_data = True
                weight_transfer_mod.data_types_verts = {'VGROUP_WEIGHTS'}
                
            weight_transfer_mod.object = weight_ob

            if arma_mod is None:
                arma_mod =obj.modifiers.new('REDTHREAD_ARMATURE', 'ARMATURE')
                if asset.armature_object is not None:
                    arma_mod.object = asset.armature_object

        return {'FINISHED'}