|
from py_slvs import slvs |
|
from mathutils import Vector |
|
|
|
# https://gist.github.com/Andrej730/5b99ed5dfcb69734bb53005c71f18813 |
|
|
|
# mapping can be found here: |
|
# https://github.com/realthunder/solvespace/blob/python/include/slvs.h |
|
type_id_mapping = { |
|
50000: 'SLVS_E_POINT_IN_3D', |
|
50001: 'SLVS_E_POINT_IN_2D', |
|
60000: 'SLVS_E_NORMAL_IN_3D', |
|
60001: 'SLVS_E_NORMAL_IN_2D', |
|
70000: 'SLVS_E_DISTANCE', |
|
80000: 'SLVS_E_WORKPLANE', |
|
80001: 'SLVS_E_LINE_SEGMENT', |
|
80002: 'SLVS_E_CUBIC', |
|
80003: 'SLVS_E_CIRCLE', |
|
80004: 'SLVS_E_ARC_OF_CIRCLE', |
|
90000: 'SLVS_E_TRANSFORM', |
|
100000: 'SLVS_C_POINTS_COINCIDENT', |
|
100001: 'SLVS_C_PT_PT_DISTANCE', |
|
100002: 'SLVS_C_PT_PLANE_DISTANCE', |
|
100003: 'SLVS_C_PT_LINE_DISTANCE', |
|
100004: 'SLVS_C_PT_FACE_DISTANCE', |
|
100005: 'SLVS_C_PT_IN_PLANE', |
|
100006: 'SLVS_C_PT_ON_LINE', |
|
100007: 'SLVS_C_PT_ON_FACE', |
|
100008: 'SLVS_C_EQUAL_LENGTH_LINES', |
|
100009: 'SLVS_C_LENGTH_RATIO', |
|
100010: 'SLVS_C_EQ_LEN_PT_LINE_D', |
|
100011: 'SLVS_C_EQ_PT_LN_DISTANCES', |
|
100012: 'SLVS_C_EQUAL_ANGLE', |
|
100013: 'SLVS_C_EQUAL_LINE_ARC_LEN', |
|
100014: 'SLVS_C_SYMMETRIC', |
|
100015: 'SLVS_C_SYMMETRIC_HORIZ', |
|
100016: 'SLVS_C_SYMMETRIC_VERT', |
|
100017: 'SLVS_C_SYMMETRIC_LINE', |
|
100018: 'SLVS_C_AT_MIDPOINT', |
|
100019: 'SLVS_C_HORIZONTAL', |
|
100020: 'SLVS_C_VERTICAL', |
|
100021: 'SLVS_C_DIAMETER', |
|
100022: 'SLVS_C_PT_ON_CIRCLE', |
|
100023: 'SLVS_C_SAME_ORIENTATION', |
|
100024: 'SLVS_C_ANGLE', |
|
100025: 'SLVS_C_PARALLEL', |
|
100026: 'SLVS_C_PERPENDICULAR', |
|
100027: 'SLVS_C_ARC_LINE_TANGENT', |
|
100028: 'SLVS_C_CUBIC_LINE_TANGENT', |
|
100029: 'SLVS_C_EQUAL_RADIUS', |
|
100030: 'SLVS_C_PROJ_PT_DISTANCE', |
|
100031: 'SLVS_C_WHERE_DRAGGED', |
|
100032: 'SLVS_C_CURVE_CURVE_TANGENT', |
|
100033: 'SLVS_C_LENGTH_DIFFERENCE' |
|
} |
|
|
|
solve_status_mapping = { |
|
0: "Successfully solved sketch.", |
|
1: "Cannot solve sketch because of inconsistent constraints, check through the failed constraints and remove the ones that contradict each other.", |
|
2: "Cannot solve sketch, system didn't converge.", |
|
3: "Cannot solve sketch because of too many unknowns.", |
|
4: "Solver failed to initialize.", |
|
5: "Some constraints seem to be redundant, this might cause an error once the constraints are no longer consistent. Check through the marked constraints and only keep what's necessary.", |
|
6: "Cannot solve sketch because of unknown failure." |
|
} |
|
|
|
|
|
V = lambda *x: Vector([float(i) for i in x]) |
|
solvesys = slvs.System() |
|
|
|
def get_entity_params(entity): |
|
if isinstance(entity, int): |
|
entity = solvesys.getEntity(entity) |
|
|
|
params = [] |
|
entity_id = entity.h |
|
param_i = 0 |
|
while True: |
|
param_id = solvesys.getEntityParam(entity_id, param_i) |
|
if param_id == 0: |
|
break |
|
params.append(param_id) |
|
param_i += 1 |
|
return params |
|
|
|
def add_point(pos, workplane=None, fixed=False): |
|
is_2d = bool(workplane) |
|
pos = pos[:2] if is_2d else pos |
|
point_group = group if not fixed else FIXED_GROUP |
|
|
|
# `solvesys.addParamV` returns index of registered parameter |
|
# value can be accessed later with `solvesys.getParam(index)` |
|
params = [solvesys.addParamV(i, group=point_group) for i in pos] |
|
|
|
# `solvesys.addPoint3d` also returns entity index |
|
# you can get it later with `solvesys.getEntity(index)` |
|
if is_2d: |
|
p = solvesys.addPoint2d(workplane, *params, group=point_group) |
|
else: |
|
p = solvesys.addPoint3d(*params, group=point_group) |
|
return p |
|
|
|
def quat_from_vector(vector): |
|
# similarly you can get vector (normal) from quaternion |
|
# Vector((0,0,1)).rotate(quat) |
|
return Vector((0,0,1)).rotation_difference(vector) |
|
|
|
def get_param_values(params): |
|
return [solvesys.getParam(p).val for p in params] |
|
|
|
def point_coords(point_entity): |
|
return Vector(get_param_values(get_entity_params(point_entity))) |
|
|
|
def aligned_distance(e1, e2, value, alignment="VERTICAL"): |
|
"""order of points is not important""" |
|
# in solve space there is no way to add aligned distance from the box |
|
# therefore we do it in 3 constraints |
|
# |
|
# first point represents base for Y / vertical constraint |
|
# second point - for X / horizontal constraint |
|
# but order doesn't matter |
|
p1, p2 = point_coords(e1), point_coords(e2) |
|
between = add_point((p2.x, p1.y), workplane) |
|
solvesys.addPointsHorizontal(between, e2, workplane, group=group) # same x |
|
solvesys.addPointsVertical(between, e1, workplane, group=group) # same y |
|
constrained_point = e1 if alignment == "VERTICAL" else e2 |
|
solvesys.addPointsDistance(value, between, constrained_point, wrkpln=workplane, group=group) |
|
|
|
# BASIC SETUP, note that base points, normals and workplane must be fixed (assigned to the FIXED_GROUP) |
|
# otherwise solver might change them trying to sovle the sketch |
|
FIXED_GROUP = 1 # index 1 is reserved to keep some entities intact |
|
group = 3 |
|
base_point_3d = add_point((0,0,0), fixed=True) |
|
base_normal = V(0,0,1) |
|
base_normal = solvesys.addNormal3dV(*quat_from_vector(base_normal), group=FIXED_GROUP) |
|
workplane = solvesys.addWorkplane(base_point_3d, base_normal, group=FIXED_GROUP) |
|
# there is no base point by default, we need to create it |
|
base_point = add_point(V(0,0), workplane, fixed=True) |
|
|
|
def case_1_2d(): |
|
# horizontal constraint example |
|
edge = ((0,0,8), (5,0,24)) |
|
edge = [e[:2] for e in edge] |
|
edge = [add_point(pos, workplane) for pos in edge] |
|
line = solvesys.addLineSegment(*edge, group=group) |
|
|
|
# set new param values, line is not parallel anymore |
|
solvesys.getParam(get_entity_params(edge[0])[1]).val = 5.0 |
|
solvesys.getParam(get_entity_params(edge[1])[1]).val = 3.0 |
|
|
|
# [Vector((0.0, 5.0)), Vector((5.0, 3.0))] |
|
print([point_coords(e) for e in edge]) |
|
|
|
solvesys.addLineHorizontal(line, wrkpln=workplane, group=group) |
|
retvalue = solvesys.solve(group=group, reportFailed=True, findFreeParams=False) |
|
print(solve_status_mapping[retvalue]) |
|
|
|
# both y equals 3 now, case solved |
|
# [Vector((0.0, 3.0)), Vector((5.0, 3.0))] |
|
print([point_coords(e) for e in edge]) |
|
|
|
def case_1_3d(): |
|
# horizontal constraint example |
|
edge = ((0,0,8), (5,0,24)) |
|
edge = [add_point(pos) for pos in edge] |
|
line = solvesys.addLineSegment(*edge, group=group) |
|
|
|
# set new param values, line is not parallel anymore |
|
solvesys.getParam(get_entity_params(edge[0])[1]).val = 5.0 |
|
solvesys.getParam(get_entity_params(edge[1])[1]).val = 3.0 |
|
|
|
# in 3d constraints we need to specify x axis |
|
x_point = add_point((1,0,0), fixed=True) |
|
x_axis = solvesys.addLineSegment(base_point_3d, x_point, group=FIXED_GROUP) |
|
|
|
solvesys.addLineHorizontal(line, wrkpln=workplane, group=group) |
|
# solvesys.addParallel(line, x_axis, group=group) |
|
|
|
# [Vector((0.0, 5.0)), Vector((5.0, 3.0))] |
|
print([point_coords(e) for e in edge]) |
|
|
|
retvalue = solvesys.solve(group=group, reportFailed=True, findFreeParams=False) |
|
print(solve_status_mapping[retvalue]) |
|
|
|
# both y equals 3 now, case solved |
|
# [Vector((0.0, 3.0)), Vector((5.0, 3.0))] |
|
print([point_coords(e) for e in edge]) |
|
print(point_coords(x_point)) |
|
|
|
def case_2_2d(): |
|
# distance constraint example |
|
coords = (5,0) |
|
point = add_point(coords, workplane) |
|
|
|
print(point_coords(base_point_3d)) |
|
print(point_coords(point)) |
|
|
|
solvesys.addPointsDistance(10, base_point_3d, point, wrkpln=workplane, group=group) |
|
retvalue = solvesys.solve(group=group, reportFailed=True, findFreeParams=False) |
|
print(solve_status_mapping[retvalue]) |
|
|
|
print(point_coords(base_point_3d)) |
|
print(point_coords(point)) |
|
|
|
def case_2_3d(): |
|
# distance constraint example |
|
use_2d = False |
|
coords = (5,0,5) |
|
point = add_point(coords) |
|
|
|
print(point_coords(base_point_3d)) |
|
print(point_coords(point)) |
|
|
|
solvesys.addPointsDistance(10, base_point_3d, point, group=group) |
|
retvalue = solvesys.solve(group=group, reportFailed=True, findFreeParams=False) |
|
print(solve_status_mapping[retvalue]) |
|
|
|
print(point_coords(base_point_3d)) |
|
print(point_coords(point)) |
|
|
|
|
|
|
|
def case_3(): |
|
# reproduced in CAD Sketcher - https://i.imgur.com/QmAqDjO.png |
|
dims = (10, 5) |
|
x_offset = 5 |
|
offset = V(0,10) # y offset to avoid failing constraints |
|
|
|
profile_1 = [V(dims[0], 0), V(-dims[0], 0)] |
|
profile_2 = [V(dims[1], 0), V(-dims[1], 0)] |
|
profile_2 = [p + offset for p in profile_2] |
|
|
|
profile_1 = [add_point(p, workplane) for p in profile_1] |
|
profile_2 = [add_point(p, workplane) for p in profile_2] |
|
|
|
base_edge = solvesys.addLineSegment(*profile_1, group=group) |
|
side_edges = [ |
|
solvesys.addLineSegment(profile_1[0], profile_2[0], group=group), |
|
solvesys.addLineSegment(profile_1[1], profile_2[1], group=group), |
|
] |
|
|
|
theta = 30 # in degrees |
|
supplementary_angle = False |
|
|
|
# CONSTRAINTS |
|
# profile 1 |
|
solvesys.addPointsDistance(dims[0]*2, *profile_1, wrkpln=workplane, group=group) |
|
solvesys.addPointsHorizontal(*profile_1, group=group, wrkpln=workplane) |
|
solvesys.addMidPoint(base_point, base_edge, group=group, wrkpln=workplane) |
|
|
|
# profile 2 |
|
solvesys.addPointsDistance(dims[1]*2, *profile_2, wrkpln=workplane, group=group) |
|
solvesys.addPointsHorizontal(*profile_2, group=group, wrkpln=workplane) |
|
aligned_distance(profile_2[1], base_point, x_offset+dims[1], "HORIZONTAL") |
|
|
|
solvesys.addAngle(theta, supplementary_angle, *side_edges, group=group) |
|
|
|
print([get_param_values(get_entity_params(p)) for p in profile_1]) |
|
print([get_param_values(get_entity_params(p)) for p in profile_2]) |
|
|
|
retvalue = solvesys.solve(group=group, reportFailed=True, findFreeParams=False) |
|
print(solve_status_mapping[retvalue]) |
|
|
|
print([get_param_values(get_entity_params(p)) for p in profile_1]) |
|
print([get_param_values(get_entity_params(p)) for p in profile_2]) |
|
|
|
case_1_3d() |