Summary
A *RIGID BODY on nodes of plane stress, plane strain or axisymmetric elements runs in ccx 2.23 (the test deck scheibe.inp, “rigid body motion of plane strain elements”, does this). As soon as one of the rigid body’s nodes is also in a node surface (*SURFACE, TYPE=NODE), ccx stops before the first increment:
*ERROR in cascade: the DOF corresponding to
node 5 in direction 3 is detected on the
dependent side of a MPC and a SPC
(exit 201). The deck has no SPC on node 5, and the surface does not have to be used anywhere: one node in an otherwise unused node surface is enough. The same deck without the surface runs, and so does the surface on a node outside the rigid body.
The SPC comes from gen3dsurf.f, which links every node of a node surface on plane elements to its expanded mid-plane node and fixes the original node in z. Section 7.129 (*SURFACE) and section 7.113 (*RIGID BODY) of the 2.23 manual mention no such constraint.
The surface can also be one that the model needs. A rigid CPS4 punch pressed onto a CPS4 block by node-to-surface contact, with the punch’s bottom nodes as the slave node surface, stops with the same message (for node 11). Section 7.22 (*CONTACT PAIR) allows a nodal slave surface (“The dependent surface can be defined as a nodal surface”). With the same nodes given as an element face surface, the deck runs.
Version
ccx 2.23 (macOS arm64). Source: master 9ba5d76 (16 Oct 2026).
Already reported or fixed?
Not reported: I found no matching GitHub issue (open or closed) or pull request in Dhondtguido/CalculiX, and no calculix.discourse.group topic that identifies the cause (searched 22 Oct 2026). Not fixed: the plane-element branch of gen3dsurf.f is unchanged on master (9ba5d76); master only renamed its node-to-element arrays.
Related:
- Error solving: SPC meaning (2025): the same stop (“node 1 in direction 3 is detected on the dependent side of a MPC and a SPC”) in a 2-D gear model (Kinematic keyword definition is the same model), whose pinion had a node-based
*COUPLINGsurface with*KINEMATIC1, 3, and whose contact node sets shared nodes with the couplings. The thread did not find the cause; the error went away after the surfaces were changed. Probably the samegen3dsurf.fz-SPC: here,rb_nosurf.inpwith its*RIGID BODYline replaced by*SURFACE, NAME=SC, TYPE=NODE/RB/*COUPLING, REF NODE=100, SURFACE=SC, CONSTRAINT NAME=C1/*KINEMATIC/1, 3stops with the same message for node 5, although section 7.27 asks for a face-based coupling surface. - Issues · Dhondtguido/CalculiX · GitHub
*RIGID BODYwith plane elements in explicit dynamics stops on linear and nonlinear MPCs depending on each other. A different stop, in a different procedure; the static decks here run without the node surface. - https://calculix.discourse.group/t/1910/5: a user adds an otherwise unused node surface in a 2-D model on purpose, to make an initial condition on a node set work. So the link between the user’s node and the expanded node that
gen3dsurf.fmakes is useful; the fix below keeps it.
Minimal input
rb_surf.inp: two CPS4 elements; the right edge (nodes 5, 6) is a rigid body, pulled in x. The rigid body’s out-of-plane motions (reference node in z, rotations about x and y) are held. Node 5 is in a node surface that nothing uses.
** two CPS4 elements; the right edge (nodes 5, 6) is a rigid body with
** reference node 100, pulled in x; node 5 is also in a node surface S
** that nothing uses
*NODE, NSET=NALL
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
5, 2., 0., 0.
6, 2., 1., 0.
100, 2.5, 0.5, 0.
101, 2.5, 0.5, 0.
*ELEMENT, TYPE=CPS4, ELSET=EALL
1, 1, 2, 3, 4
2, 2, 5, 6, 3
*NSET, NSET=RB
5, 6
*SURFACE, TYPE=NODE, NAME=S
5
*RIGID BODY, NSET=RB, REF NODE=100, ROT NODE=101
*MATERIAL, NAME=M
*ELASTIC
1000., 0.3
*SOLID SECTION, ELSET=EALL, MATERIAL=M
1.
*BOUNDARY
1, 1, 2
4, 1, 1
100, 3, 3
101, 1, 2
*STEP
*STATIC
*CLOAD
100, 1, 1.
*NODE PRINT, NSET=RB
U
*END STEP
Variants:
rb_nosurf.inp: the two lines*SURFACE, TYPE=NODE, NAME=Sand5removed.rb_surf_off.inp: the surface holds node 1 (not in the rigid body) instead of node 5.
rb_contact.inp: a rigid CPS4 punch (element 2) pushed onto a CPS4 block (element 1) by node-to-surface contact; the punch’s bottom nodes form the slave node surface.
** a CPS4 block (element 1, bottom held) and a rigid CPS4 punch on top of it
** (element 2, *RIGID BODY, reference node 100); node-to-surface contact with
** the punch's bottom nodes 11, 12 as a node surface (slave) and the block's
** top edge as master; the punch is pushed down by 0.01
*NODE, NSET=NALL
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
11, 0.25, 1., 0.
12, 0.75, 1., 0.
13, 0.75, 1.5, 0.
14, 0.25, 1.5, 0.
100, 0.5, 1.5, 0.
101, 0.5, 1.5, 0.
*ELEMENT, TYPE=CPS4, ELSET=EB
1, 1, 2, 3, 4
*ELEMENT, TYPE=CPS4, ELSET=EP
2, 11, 12, 13, 14
*ELSET, ELSET=EALL
EB, EP
*NSET, NSET=PBOT
11, 12
*SURFACE, TYPE=NODE, NAME=SLAVE
PBOT
*SURFACE, NAME=MASTER
1, S3
*RIGID BODY, ELSET=EP, REF NODE=100, ROT NODE=101
*MATERIAL, NAME=M
*ELASTIC
1000., 0.3
*SOLID SECTION, ELSET=EALL, MATERIAL=M
1.
*SURFACE INTERACTION, NAME=SI
*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR
1.e5
*CONTACT PAIR, INTERACTION=SI, TYPE=NODE TO SURFACE
SLAVE, MASTER
*BOUNDARY
1, 1, 2
2, 2, 2
100, 1, 1
100, 3, 3
101, 1, 3
*STEP
*STATIC
*BOUNDARY
100, 2, 2, -0.01
*NODE PRINT, NSET=NALL
U
*END STEP
rb_contact_face.inp: the slave surface given as the punch’s bottom face instead:
-*SURFACE, TYPE=NODE, NAME=SLAVE
-PBOT
+*SURFACE, NAME=SLAVE
+2, S1
How to run
ccx rb_surf
ccx rb_nosurf
ccx rb_surf_off
ccx rb_contact
ccx rb_contact_face
Expected
The surface in rb_surf is not used, so rb_surf should give what rb_nosurf gives. rb_contact should run like rb_contact_face, since both name the same slave nodes.
Actual
rb_surf and rb_contact stop with exit status 201:
*ERROR in cascade: the DOF corresponding to
node 5 in direction 3 is detected on the
dependent side of a MPC and a SPC
(rb_contact: node 11). rb_nosurf and rb_surf_off run (exit 0) and print the same displacements:
5 1.967819E-03 -1.417759E-04 0.000000E+00
6 1.967819E-03 -1.417759E-04 0.000000E+00
rb_contact_face runs (exit 0): the punch nodes 11-14 move -1.000000E-02 in y, and the block’s top nodes 3 and 4 move -9.801560E-03 and -9.805991E-03.
Where it goes wrong
After expanding the plane elements, src/gen3delem.f calls gen3dsurf.f in the first step (master line 453). src/gen3dsurf.f goes through every set whose name ends in ‘S’, which is how a node surface is stored (line 57), and for each node of a plane, plane strain or axisymmetric element:
- ties its x and y to the expanded mid-plane node by MPCs (lines 206-250);
- fixes the original node in z (lines 252-264):
! fixing the original node in z-direction
!
val=0.d0
k=3
if(nam.gt.0) iamplitude=0
type='M'
call bounadd(node,k,k,val,nodeboun,
The comment at lines 193-199 gives the reason: the original node may end up in a contact spring element, and its z DOF would otherwise be free and make the system singular.
A *RIGID BODY makes nonlinear MPCs for all three translations of each member node, with the member’s DOF as the dependent term; for a plane-element node that includes z. gen3dsurf.f adds its z-SPC without checking whether that DOF is already the dependent of an MPC, and cascade.c (lines 76-90) then stops on the DOF that is both.
Section 6.2.22 (CPS8 and CPS8R) says: “The displacement in z-direction of the midplane is zero. This condition is introduced in the form of SPC’s. MPC’s must not be defined in z-direction!” As I read it, that concerns the expanded mid-plane nodes, not the user’s node. When ccx links a user’s node of a plane element to its mid-plane node, it leaves z out on purpose: gen3dmpc.f (lines 403-408, the test idir.ne.3) and gen3dsurf.f (lines 209-214, do idir=1,2) link only x and y. So the rigid body’s z-MPC stays on the user’s node and does not touch the mid-plane SPCs. That is why rb_nosurf and scheibe.inp run. The only conflict is the z-SPC that gen3dsurf.f puts on the same DOF.
The check does not depend on whether a card uses the surface: every node surface is processed.
Fix
Possible fix (not built or tested): in gen3dsurf.f, skip the z-SPC when DOF 3 of the original node is already the dependent DOF of an MPC (look up 8*(node-1)+3 in ikmpc, as the routine already does for the mid-node DOFs). The MPC then already gives that DOF a value, which is what the SPC is meant to do for contact spring elements. The x/y link to the mid-plane node stays as it is.
Workaround: leave rigid-body nodes of plane elements out of node surfaces; for contact, give the slave surface as element faces.