From 0c650783315066f3e1e6af7967a95c4ee8e5200e Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sun, 31 May 2026 13:00:12 +0200 Subject: Place two assemblies at the same time It's drawing the box correctly, but some of the constraints seem wonky. I don't think this is correct, but it is sort of working. --- examples/quad.c | 2 +- inc/cad/construction.h | 6 ++ src/construction.c | 93 +++++++++++++++- src/solve.c | 287 +++++++++++++++++++++++++++++++++---------------- 4 files changed, 292 insertions(+), 96 deletions(-) diff --git a/examples/quad.c b/examples/quad.c index 878fba3..a22e872 100644 --- a/examples/quad.c +++ b/examples/quad.c @@ -17,6 +17,7 @@ void draw_from_constraints(struct constraints *c, struct topology *t, struct can } place_points(&drawing, params); + dump_program(&drawing, params); free(params); reconstruct_drawing(c, assemblies, &assemblies_num); @@ -45,7 +46,6 @@ int main(int argc, char *argv[]) { struct component l4 = {.type = COM_LINE}; add_constraint(&constraints, (struct constraint[]){ - // Make a triangle PP_DISTANCE(&p1, &p2, 10), PP_DISTANCE(&p4, &p1, 20), diff --git a/inc/cad/construction.h b/inc/cad/construction.h index 71add62..ea784cf 100644 --- a/inc/cad/construction.h +++ b/inc/cad/construction.h @@ -21,6 +21,11 @@ enum operation { // Handle assemblies CMD_IMPORT_POINT_LINE, + CMD_IMPORT_LINE_LINE, + + // Measure elements + CMD_MEASURE_POINT_LINE_DISTANCE, + CMD_MEASURE_LINE_LINE_ANGLE, }; struct point { @@ -87,6 +92,7 @@ struct drawing { struct element* insert_cmd(struct drawing *drawing, struct command cmd); void place_points(struct drawing *drawing, double inputs[]); +void dump_program(struct drawing *drawing, double inputs[]); void free_drawing(struct drawing *drawing); bool circle_line_intersect(struct circle circle, struct line line, uint8_t root, struct point *point); diff --git a/src/construction.c b/src/construction.c index 33f37ad..f175f79 100644 --- a/src/construction.c +++ b/src/construction.c @@ -87,6 +87,8 @@ static void transform_part(struct command *cmd, const struct command *last_cmd, switch(cmd->op) { case CMD_VALUE_INPUT: case CMD_OFFSET_INPUT: + case CMD_MEASURE_POINT_LINE_DISTANCE: + case CMD_MEASURE_LINE_LINE_ANGLE: break; case CMD_LINE_X: case CMD_LINE_POINT_POINT: @@ -127,6 +129,7 @@ static void transform_part(struct command *cmd, const struct command *last_cmd, break; case CMD_IMPORT_POINT_LINE: + case CMD_IMPORT_LINE_LINE: transform_part(cmd->d->first_command, cmd->d->last_command, transform); break; } @@ -137,6 +140,64 @@ static void transform_part(struct command *cmd, const struct command *last_cmd, } } +void dump_program(struct drawing *drawing, double inputs[]) { + for(struct command *current = drawing->root; current != NULL; current = current->next) { + switch(current->op) { + case CMD_VALUE_INPUT: { + fprintf(stderr, "%p -> LIT(%d, %f) = %f\n", ¤t->result, current->dir, inputs[current->index], current->result.value); + }break; + case CMD_OFFSET_INPUT: { + fprintf(stderr, "%p -> OFF(%f, %d, %f) = %f\n", ¤t->result, current->arg1->value, current->dir, inputs[current->index], current->result.value); + }break; + case CMD_ORIGIN: { + fprintf(stderr, "%p -> ZER() = <%f, %f>\n", ¤t->result, current->result.point.pos[0], current->result.point.pos[1]); + }break; + case CMD_LINE_X: { + fprintf(stderr, "%p -> LNX() = (%fx + %fy + %f = 0)\n", ¤t->result, current->result.line.norm[0], current->result.line.norm[1], current->result.line.C); + }break; + case CMD_CIRCLE_CENTER_RADIUS: { + fprintf(stderr, "%p -> CCR(%p, %p) = ((x-%f)^2 + (y-%f)^2 = %f^2)\n", ¤t->result, current->arg1, current->arg2, current->result.circle.center[0], current->result.circle.center[1], current->result.circle.radius); + }break; + case CMD_LINE_POINT_POINT: { + fprintf(stderr, "%p -> LPP(%p, %p) = (%fx + %fy + %f = 0)\n", ¤t->result, current->arg1, current->arg2, current->result.line.norm[0], current->result.line.norm[1], current->result.line.C); + }break; + case CMD_LINE_POINT_LINE_ANGLE: { + fprintf(stderr, "%p -> PLA(%p, %p, %p) = (%fx + %fy + %f = 0)\n", ¤t->result, current->arg1, current->arg2, current->arg3, current->result.line.norm[0], current->result.line.norm[1], current->result.line.C); + }break; + case CMD_LINE_LINE_DISTANCE_PARALLEL: { + fprintf(stderr, "%p -> LDP(%p, %p) = (%fx + %fy + %f = 0)\n", ¤t->result, current->arg1, current->arg2, current->result.line.norm[0], current->result.line.norm[1], current->result.line.C); + }break; + case CMD_LINE_CIRCLE_CIRCLE_TANGENT: { + fprintf(stderr, "%p -> CCT(%p, %p) = (%fx + %fy + %f = 0)\n", ¤t->result, current->arg1, current->arg2, current->result.line.norm[0], current->result.line.norm[1], current->result.line.C); + }break; + case CMD_POINT_CIRCLE_LINE: { + fprintf(stderr, "%p -> PCL(%p, %p) = <%f, %f>\n", ¤t->result, current->arg1, current->arg2, current->result.point.pos[0], current->result.point.pos[1]); + }break; + case CMD_POINT_CIRCLE_CIRCLE: { + fprintf(stderr, "%p -> PCC(%p, %p) = <%f, %f>\n", ¤t->result, current->arg1, current->arg2, current->result.point.pos[0], current->result.point.pos[1]); + }break; + case CMD_POINT_LINE_LINE: { + fprintf(stderr, "%p -> PLL(%p, %p) = <%f, %f>\n", ¤t->result, current->arg1, current->arg2, current->result.point.pos[0], current->result.point.pos[1]); + }break; + case CMD_CIRCLE_CENTER_POINT: { + fprintf(stderr, "%p -> CCR(%p, %p) = ((x-%f)^2 + (y-%f)^2 = %f^2)\n", ¤t->result, current->arg1, current->arg2, current->result.circle.center[0], current->result.circle.center[1], current->result.circle.radius); + }break; + case CMD_IMPORT_POINT_LINE: { + fprintf(stderr, "%p -> IMP(%p, %p, %p, %p) = \n", ¤t->result, current->arg1, current->arg2, current->attachp, current->attachl); + }break; + case CMD_IMPORT_LINE_LINE: { + // Deprecated + }break; + case CMD_MEASURE_POINT_LINE_DISTANCE: { + fprintf(stderr, "%p -> MPL(%p, %p) = %f\n", ¤t->result, current->arg1, current->arg2, current->result.value); + }break; + case CMD_MEASURE_LINE_LINE_ANGLE: { + fprintf(stderr, "%p -> MLL(%p, %p) = %f\n", ¤t->result, current->arg1, current->arg2, current->result.value); + }break; + } + } +} + void place_points(struct drawing *drawing, double inputs[]) { struct command *outer_object = drawing->root; @@ -272,28 +333,50 @@ void place_points(struct drawing *drawing, double inputs[]) { assert(current->d != NULL); assert(current->attachp != NULL); assert(current->attachl != NULL); + assert(current->attachp->type == ETYPE_POINT); + assert(current->attachl->type == ETYPE_LINE); double theta; // Align the two lines theta = atan2(current->arg2->line.norm[1], current->arg2->line.norm[0]) - atan2(current->attachl->line.norm[1], current->attachl->line.norm[0]); - fprintf(stderr, "%f\n", theta / M_PI * 180.0); mat3 transform; glm_mat3_identity(transform); - glm_translate2d(transform, current->attachp->point.pos); + glm_translate2d(transform, current->arg1->point.pos); glm_rotate2d(transform, theta); { vec2 negative_translate; - glm_vec2_negate_to(current->arg1->point.pos, negative_translate); + glm_vec2_negate_to(current->attachp->point.pos, negative_translate); glm_translate2d(transform, negative_translate); } - fprintf(stderr, "Assembly %p %p %p %f\n", current->d, current->d->first_command, current->d->last_command, theta); + fprintf(stderr, "Assembly %p %p %p %f %f %f\n", current->d, current->d->first_command, current->d->last_command, transform[2][0], transform[2][1], theta); transform_part(current->d->first_command, current->d->last_command, transform); }break; + case CMD_IMPORT_LINE_LINE: { + abort(); + }break; + case CMD_MEASURE_POINT_LINE_DISTANCE: { + assert(current->arg1->type == ETYPE_POINT); + assert(current->arg2->type == ETYPE_LINE); + assert(current->result.type == ETYPE_VALUE); + + double dot = glm_vec2_dot(current->arg2->line.norm, current->arg1->point.pos); + current->result.value = (dot + current->arg2->line.C) / glm_vec2_norm(current->arg2->line.norm); + fprintf(stderr, "Measured Distance %f\n", current->result.value); + }break; + case CMD_MEASURE_LINE_LINE_ANGLE: { + assert(current->arg1->type == ETYPE_LINE); + assert(current->arg2->type == ETYPE_LINE); + assert(current->result.type == ETYPE_VALUE); + + double theta = atan2(current->arg1->line.norm[1], current->arg1->line.norm[0]) - atan2(current->arg2->line.norm[1], current->arg2->line.norm[0]); + current->result.value = theta; + fprintf(stderr, "Measured Angle %f\n", current->result.value); + }break; } // printf("%fx + %fy + %f = 0\n", cmd[2].line.norm[0], cmd[2].line.norm[1], cmd[2].line.C); } @@ -323,7 +406,7 @@ void place_points(struct drawing *drawing, double inputs[]) { glm_translate2d(transform, negative_translate); } - fprintf(stderr, "W %f %f\n", origin->result.point.pos[0], origin->result.point.pos[1]); + fprintf(stderr, "W %f %f %f\n", origin->result.point.pos[0], origin->result.point.pos[1], theta); transform_part(outer_object, NULL, transform); } } diff --git a/src/solve.c b/src/solve.c index 353f052..0eab611 100644 --- a/src/solve.c +++ b/src/solve.c @@ -63,15 +63,15 @@ static void add_frontier(struct component *component) { component->fixed = true; } -static void build_angle_point_line(struct drawing *drawing, struct constraint *constraints, size_t index_i, size_t index_j, struct component *local_i, struct component *local_j, struct component *oppo_i) { - assert(local_i->e != NULL); - assert(local_j->e != NULL); - assert(local_i->type == COM_LINE); - assert(local_j->type == COM_POINT); +static void build_angle_point_line2(struct drawing *drawing, struct constraint *constraints, size_t index_i, size_t index_j, struct element *local_i, struct element *local_j, bool inverse, struct element **oppo_i) { + assert(local_i != NULL); + assert(local_j != NULL); + assert(local_i->type == ETYPE_LINE); + assert(local_j->type == ETYPE_POINT); struct element *theta = insert_cmd(drawing, (struct command){ .op = CMD_VALUE_INPUT, .index = index_i, - .dir = constraints[index_i].forward, + .dir = constraints[index_i].forward ^ inverse, .result.type = ETYPE_VALUE, }); @@ -80,7 +80,7 @@ static void build_angle_point_line(struct drawing *drawing, struct constraint *c .op = CMD_OFFSET_INPUT, .arg1 = theta, .index = p->i, - .dir = p->direction, + .dir = p->direction ^ inverse, .result.type = ETYPE_VALUE, }); } @@ -88,29 +88,33 @@ static void build_angle_point_line(struct drawing *drawing, struct constraint *c struct element *d = insert_cmd(drawing, (struct command){ .op = CMD_VALUE_INPUT, .index = index_j, - .dir = constraints[index_i].forward, + .dir = constraints[index_i].forward ^ inverse, .result.type = ETYPE_VALUE, }); struct element* l = insert_cmd(drawing, (struct command){ .op = CMD_LINE_POINT_LINE_ANGLE, - .hidden = !oppo_i->show_when_placed, + .hidden = false, .result.type = ETYPE_LINE, - .arg1 = local_j->e, - .arg2 = local_i->e, + .arg1 = local_j, + .arg2 = local_i, .arg3 = theta, }); assert(l != NULL); - oppo_i->e = insert_cmd(drawing, (struct command){ + *oppo_i = insert_cmd(drawing, (struct command){ .op = CMD_LINE_LINE_DISTANCE_PARALLEL, - .hidden = !oppo_i->show_when_placed, + .hidden = false, .result.type = ETYPE_LINE, .arg1 = l, .arg2 = d, }); } +static void build_angle_point_line(struct drawing *drawing, struct constraint *constraints, size_t index_i, size_t index_j, struct component *local_i, struct component *local_j, struct component *oppo_i) { + return build_angle_point_line2(drawing, constraints, index_i, index_j, local_i->e, local_j->e, false, &oppo_i->e); +} + struct solve_step { size_t i; size_t j; @@ -549,7 +553,7 @@ static size_t build_triangles(struct constraints *constraints_in, struct compone if(possibly_angle_c == NULL) continue; - printf("Assembly %p and %p, %d %d %s %s\n", ein_a, ein_b, f1_c, f2_c, constraint_type_name[not_angle_c->type], constraint_type_name[possibly_angle_c->type]); + fprintf(stderr, "Assembly %p and %p, %d %d %s %s\n", ein_a, ein_b, f1_c, f2_c, constraint_type_name[not_angle_c->type], constraint_type_name[possibly_angle_c->type]); // We've fixed the subcomponents, so we have to fix // the whole thing @@ -751,6 +755,65 @@ static void draw_for_subassembly(struct constraint *constraints, size_t fix, str }); } +void point_from_line_and_distance(struct drawing *drawing, struct constraint *constraints, size_t c1, size_t c2, struct element *line, struct element *point, int root, struct element **result) { + assert(line->type == ETYPE_LINE); + assert(point->type == ETYPE_POINT); + + struct element *d1 = insert_cmd(drawing, (struct command){ + .op = CMD_VALUE_INPUT, + .index = c1, + .dir = constraints[c1].forward, + .result.type = ETYPE_VALUE, + }); + struct element *d2 = insert_cmd(drawing, (struct command){ + .op = CMD_VALUE_INPUT, + .index = c2, + .dir = constraints[c2].forward, + .result.type = ETYPE_VALUE, + }); + + struct element *l = insert_cmd(drawing, (struct command){ + .op = CMD_LINE_LINE_DISTANCE_PARALLEL, + .hidden = false, + .result.type = ETYPE_LINE, + .arg1 = line, + .arg2 = d1, + }); + + struct element *c = insert_cmd(drawing, (struct command){ + .op = CMD_CIRCLE_CENTER_RADIUS, + .hidden = false, + .result.type = ETYPE_CIRCLE, + .arg1 = point, + .arg2 = d2, + }); + + *result = insert_cmd(drawing, (struct command){ + .op = CMD_POINT_CIRCLE_LINE, + .hidden = false, + .root = root, + .result.type = ETYPE_POINT, + .arg1 = c, + .arg2 = l, + }); +} + +#define expand_constraint(NAME, IDX) \ + struct constraint *NAME = &constraints[IDX]; \ + struct component *NAME##_local; \ + struct component *NAME##_oppo; \ + do { \ + if(IDX##_forward) { \ + NAME##_local = constraints[IDX].c1; \ + NAME##_oppo = constraints[IDX].c2; \ + } else { \ + NAME##_local = constraints[IDX].c2; \ + NAME##_oppo = constraints[IDX].c1; \ + } \ + } while(0) + + + static void draw_for_double_subassembly(struct constraint *constraints, size_t fix, struct solve_step *step, struct drawing *drawing, struct subassembly *assembly) { assert(step->assembly != NULL); assert(step->assembly2 != NULL); @@ -759,7 +822,123 @@ static void draw_for_double_subassembly(struct constraint *constraints, size_t f // two constraints each, but are also mutually constrained by two // constraints. - abort(); + expand_constraint(a1, step->i); + expand_constraint(a2, step->j); + + expand_constraint(b1, step->k); + expand_constraint(b2, step->x); + + expand_constraint(c1, step->y); + expand_constraint(c2, step->z); + + // Lets solve for a pretty static case first + assert(a1->type == CT_POINT_POINT_DISTANCE); + assert(a2->type == CT_POINT_LINE_DISTANCE); + + assert(b1->type == CT_POINT_LINE_DISTANCE); + assert(b2->type == CT_LINE_LINE_ANGLE); + + assert(c1->type == CT_POINT_LINE_DISTANCE); + assert(c2->type == CT_LINE_LINE_ANGLE); + + // All the constraints form triangles, not necessarily required, but + // simpler + assert(a1_oppo == a2_oppo); + assert(a1_local != a2_local); + assert(b1_oppo == b2_oppo); + assert(b1_local != b2_local); + assert(c1_oppo == c2_oppo); + assert(c1_local != c2_local); + + // The basic idea here is to create some new points for ax_oppo and bx_oppo + // which we can then use to place a circle or line + struct element *ax_oppo_locally = {}; + point_from_line_and_distance(drawing, constraints, step->j, step->i, a2_local->e, a1_local->e, constraints[step->j].c2 == a2_local, &ax_oppo_locally); + + struct element *bx_oppo_locally = {}; + build_angle_point_line2(drawing, constraints, step->x, step->k, b2_local->e, b1_local->e, true, &bx_oppo_locally); + + //cx's naming is strange, the two local points are in bx and the opposing + //point is in ax. We know that since ax was built before bx and had the two + //local elements been in ax, we would have included the point as well. + // + struct element *assembly1_distance = insert_cmd(drawing, (struct command){ + .op = CMD_MEASURE_POINT_LINE_DISTANCE, + .hidden = true, + .result.type = ETYPE_VALUE, + .arg1 = a1_oppo->e, + .arg2 = c1_oppo->e, + }); + + // This line isn't really in our scope at this point. This is technically + // dubious, but it works due to the current implementation + struct element *cx_oppo_bx = {}; + build_angle_point_line2(drawing, constraints, step->z, step->y, c2_local->e, c1_local->e, true, &cx_oppo_bx); + + struct element *assembly2_angle = insert_cmd(drawing, (struct command){ + .op = CMD_MEASURE_LINE_LINE_ANGLE, + .hidden = true, + .result.type = ETYPE_VALUE, + .arg1 = b1_oppo->e, + .arg2 = cx_oppo_bx, + }); + + struct element* cx_oppo_direction_locally; + { + struct element* l = insert_cmd(drawing, (struct command){ + .op = CMD_LINE_POINT_LINE_ANGLE, + .hidden = false, + .result.type = ETYPE_LINE, + .arg1 = ax_oppo_locally, + .arg2 = bx_oppo_locally, + .arg3 = assembly2_angle, + }); + + cx_oppo_direction_locally = insert_cmd(drawing, (struct command){ + .op = CMD_LINE_LINE_DISTANCE_PARALLEL, + .hidden = false, + .result.type = ETYPE_LINE, + .root = 1, + .arg1 = l, + .arg2 = assembly1_distance, + }); + } + + insert_cmd(drawing, (struct command){ + .op = CMD_IMPORT_POINT_LINE, + .arg1 = ax_oppo_locally, + .arg2 = cx_oppo_direction_locally, + .d = step->assembly, + .attachp = a1_oppo->e, + .attachl = c1_oppo->e, + }); + + { + struct element *local_intersection = insert_cmd(drawing, (struct command){ + .op = CMD_POINT_LINE_LINE, + .hidden = true, + .result.type = ETYPE_POINT, + .arg1 = bx_oppo_locally, + .arg2 = cx_oppo_direction_locally, + }); + + struct element *subassembly2_intersection = insert_cmd(drawing, (struct command){ + .op = CMD_POINT_LINE_LINE, + .hidden = true, + .result.type = ETYPE_POINT, + .arg1 = b1_oppo->e, + .arg2 = cx_oppo_bx, + }); + + insert_cmd(drawing, (struct command){ + .op = CMD_IMPORT_POINT_LINE, + .arg1 = local_intersection, + .arg2 = bx_oppo_locally, + .d = step->assembly2, + .attachp = subassembly2_intersection, + .attachl = b1_oppo->e, + }); + } } static void draw_solution(struct constraint *constraints, size_t fix, struct solve_step* steps, size_t steps_num, struct drawing *drawing, struct subassembly *assembly) { @@ -988,43 +1167,7 @@ static void draw_solution(struct constraint *constraints, size_t fix, struct sol && local_j->type == COM_POINT) { assert(oppo_i->type == COM_POINT); - struct element *d1 = insert_cmd(drawing, (struct command){ - .op = CMD_VALUE_INPUT, - .index = step->i, - .dir = constraints[step->i].forward, - .result.type = ETYPE_VALUE, - }); - struct element *d2 = insert_cmd(drawing, (struct command){ - .op = CMD_VALUE_INPUT, - .index = step->j, - .dir = constraints[step->j].forward, - .result.type = ETYPE_VALUE, - }); - - struct element *l = insert_cmd(drawing, (struct command){ - .op = CMD_LINE_LINE_DISTANCE_PARALLEL, - .hidden = !shown, - .result.type = ETYPE_LINE, - .arg1 = local_i->e, - .arg2 = d1, - }); - - struct element *c = insert_cmd(drawing, (struct command){ - .op = CMD_CIRCLE_CENTER_RADIUS, - .hidden = !shown, - .result.type = ETYPE_CIRCLE, - .arg1 = local_j->e, - .arg2 = d2, - }); - - oppo_i->e = insert_cmd(drawing, (struct command){ - .op = CMD_POINT_CIRCLE_LINE, - .hidden = !shown, - .root = constraints[step->j].c2 == local_j, - .result.type = ETYPE_POINT, - .arg1 = c, - .arg2 = l, - }); + point_from_line_and_distance(drawing, constraints, step->i, step->j, local_i->e, local_j->e, constraints[step->j].c2 == local_j, &oppo_i->e); } else if(constraints[step->i].type == CT_POINT_POINT_DISTANCE && local_i->type == COM_POINT && constraints[step->j].type == CT_POINT_LINE_DISTANCE @@ -1032,43 +1175,7 @@ static void draw_solution(struct constraint *constraints, size_t fix, struct sol // @COPYPASTA: Taken from above but with params swapped assert(oppo_i->type == COM_POINT); - struct element *d1 = insert_cmd(drawing, (struct command){ - .op = CMD_VALUE_INPUT, - .index = step->i, - .dir = constraints[step->i].forward, - .result.type = ETYPE_VALUE, - }); - struct element *d2 = insert_cmd(drawing, (struct command){ - .op = CMD_VALUE_INPUT, - .index = step->j, - .dir = constraints[step->j].forward, - .result.type = ETYPE_VALUE, - }); - - struct element *l = insert_cmd(drawing, (struct command){ - .op = CMD_LINE_LINE_DISTANCE_PARALLEL, - .hidden = !shown, - .result.type = ETYPE_LINE, - .arg1 = local_j->e, - .arg2 = d2, - }); - - struct element *c = insert_cmd(drawing, (struct command){ - .op = CMD_CIRCLE_CENTER_RADIUS, - .hidden = !shown, - .result.type = ETYPE_CIRCLE, - .arg1 = local_i->e, - .arg2 = d1, - }); - - oppo_i->e = insert_cmd(drawing, (struct command){ - .op = CMD_POINT_CIRCLE_LINE, - .hidden = !shown, - .root = constraints[step->j].c2 == local_i, - .result.type = ETYPE_POINT, - .arg1 = c, - .arg2 = l, - }); + point_from_line_and_distance(drawing, constraints, step->j, step->i, local_j->e, local_i->e, constraints[step->j].c2 == local_i, &oppo_i->e); } else { CRASH("Unknown constraint combination %s and %s\n", constraint_type_name[constraints[step->i].type], constraint_type_name[constraints[step->j].type]); } @@ -1119,7 +1226,7 @@ bool solve_constraints(struct constraints *constraints, struct drawing *drawing, size_t fix; while(fix_first(constraints->elements, constraints->length, &fix)) { // Build triangles on that root - assemblies[*assemblies_num].steps = malloc(sizeof(struct solve_step) * constraints->length); + assemblies[*assemblies_num].steps = calloc(1, sizeof(struct solve_step) * constraints->length); assemblies[*assemblies_num].articulation = malloc(sizeof(struct component*) * constraints->length); assemblies[*assemblies_num].articulation_position = malloc(sizeof(struct element*) * constraints->length); assemblies[*assemblies_num].steps_num = build_triangles(constraints, components, dest_num, fix, *assemblies_num+1, &assemblies[*assemblies_num]); -- cgit v1.2.3