In the meantime, the solution has been found, here’s the script in case someone else needs it. 
In this script, the distance between the objects doesn't matter — the rope will always have a fixed length.

In this script, the distance between the objects doesn't matter — the rope will always have a fixed length.
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
public class FixedLenghtRope : MonoBehaviour
{
public ObiSolver solver;
public ObiCollider character;
public ObiCollider fixedHookTarget;
public Material material;
public ObiRopeSection section;
public float RopeLenght;
public float Streched;
public float hookResolution = 0.5f;
public int particlePoolSize = 100;
private ObiRope rope;
private ObiRopeBlueprint blueprint;
private ObiRopeLineRenderer ropeRenderer;
private bool attached = false;
public float fixedRopeLength = 5f;
void Awake()
{
}
private void OnDestroy()
{
DestroyImmediate(blueprint);
}
private void LaunchHook()
{
GameObject ropeObject = new GameObject("ObiRope");
ropeObject.transform.SetParent(solver.transform);
rope = ropeObject.AddComponent<ObiRope>();
ropeRenderer = rope.gameObject.AddComponent<ObiRopeLineRenderer>();
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
ropeRenderer.material = material;
// Setup a blueprint for the rope:
blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.5f;
blueprint.pooledParticles = particlePoolSize;
// Tweak rope parameters:
rope.maxBending = 0.02f;
StartCoroutine(AttachHook());
}
private IEnumerator AttachHook()
{
yield return null;
// Clear pin constraints:
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
Vector3 targetPosition = fixedHookTarget.transform.position;
Vector3 localTargetPosition = rope.transform.InverseTransformPoint(targetPosition);
// Procedurally generate the rope path (a straight line with fixed length):
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook start");
blueprint.path.AddControlPoint(localTargetPosition.normalized * fixedRopeLength, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook end");
blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return blueprint.Generate();
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
rope.ropeBlueprint = blueprint;
// wait for the solver to load the rope, after the next physics step:
yield return new WaitForFixedUpdate();
yield return null;
// Pin both ends of the rope:
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.elements[0].particle1, character, transform.localPosition, Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.AddConstraint(rope.elements[rope.elements.Count - 1].particle2, fixedHookTarget,
fixedHookTarget.transform.InverseTransformPoint(fixedHookTarget.transform.position), Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
attached = true;
}
private void DetachHook()
{
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
Destroy(rope.gameObject);
// rope.GetComponent<ObiRopeLineRenderer>().enabled = false;
attached = false;
}
void Update()
{
if (attached == true)
{
RopeLenght = rope.CalculateLength();
Streched = rope.CalculateLength() / rope.restLength;
}
if (Input.GetMouseButtonDown(0))
{
LaunchHook();
}
if (Input.GetMouseButtonDown(1))
{
DetachHook();
}
}
}