*DLOAD BX/BY/BZ on shell elements becomes a pressure along the normal: wrong direction, 1/t too large, sign set by the node order (exit 0, no message)

Summary

*DLOAD accepts the body force labels BX, BY and BZ for any element type (dloads.f, label check). On solid elements e_c3d.f integrates them over the element volume as a force per unit volume along global x, y or z. Beams keep the label too, and so do membranes in the first step.

On shell elements dloads.f renames every label except EDNORx and I to P1. A body force B therefore becomes a pressure B on the shell, acting along the shell normal. Three things go wrong at once, without a message:

  • Direction: BX and BY (in-plane loads on a flat plate in the x-y plane) bend the plate out of plane. BZ follows the shell normal, not global z.
  • Magnitude: the value is applied per unit area instead of per unit volume, so the load is 1/t times too large (10 times for t = 0.1).
  • Node order: two identical plates under the same BZ deflect in opposite directions when one plate’s connectivity runs the other way round.

The same card on M3D4 membranes is a body force in the first step and a pressure from the second step on: after the first step the membranes are expanded and match the shell test.

The labels BX, BY and BZ are not in section 7.44 (*DLOAD) of the 2.23 manual. Their intended meaning is in the LOGBOOK of the source tree, entry of 4 April 2009: “provided for BX, BY and BZ as volumetric loading including nonuniform loading (BXNU…)”. ccx itself agrees with that everywhere except on shells: the same label is a body force per unit volume on solids, beams and first-step membranes. On shells the result depends on the node numbering, and a force per unit volume is used as a force per unit area.

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 (searched 21 Oct 2026). Not fixed: dloads.f is unchanged on master (9ba5d76) and still renames the label in both shell branches.

Related:

Minimal input

shell_body.inp: four separate one-element S4 cantilevers (1 x 1, t = 0.1, E = 1000, nu = 0, rho = 1), clamped at x = 0. Elements 1 and 2 are identical plates, except that element 2 lists its nodes the other way round (normal along -z). Element 3 carries the same body force as GRAV, as the reference.

*HEADING
Four one-element S4 cantilevers (1 x 1, t = 0.1, rho = 1), clamped at x = 0
*NODE
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
11, 0., 2., 0.
12, 1., 2., 0.
13, 1., 3., 0.
14, 0., 3., 0.
21, 0., 4., 0.
22, 1., 4., 0.
23, 1., 5., 0.
24, 0., 5., 0.
31, 0., 6., 0.
32, 1., 6., 0.
33, 1., 7., 0.
34, 0., 7., 0.
*ELEMENT, TYPE=S4, ELSET=EALL
1, 1, 2, 3, 4
2, 11, 14, 13, 12
3, 21, 22, 23, 24
4, 31, 32, 33, 34
*SHELL SECTION, ELSET=EALL, MATERIAL=M
0.1
*MATERIAL, NAME=M
*ELASTIC
1000., 0.
*DENSITY
1.
*NSET, NSET=FIX
1, 4, 11, 14, 21, 24, 31, 34
*NSET, NSET=TIP
2, 3, 12, 13, 22, 23, 32, 33
*BOUNDARY
FIX, 1, 6
*STEP
*STATIC
*DLOAD
1, BZ, 2.
2, BZ, 2.
3, GRAV, 2., 0., 0., 1.
4, BX, 2.
*NODE PRINT, NSET=TIP
U
*END STEP
membrane_step.inp: the same BX card on an M3D4 membrane, in step 1 and in step 2

One M3D4 element (1 x 1, t = 0.1), clamped at x = 0, all nodes held in z. Step 1 has no load; step 2 has BX, 2.. With the *DLOAD lines moved into step 1, the tip moves U1 = 1.000000E-03 (closed form B L^2 / (2 E)).

*NODE
1, 0., 0., 0.
2, 1., 0., 0.
3, 1., 1., 0.
4, 0., 1., 0.
*ELEMENT, TYPE=M3D4, ELSET=EALL
1, 1, 2, 3, 4
*MEMBRANE SECTION, ELSET=EALL, MATERIAL=M
0.1
*MATERIAL, NAME=M
*ELASTIC
1000., 0.
*NSET, NSET=FIX
1, 4
*NSET, NSET=TIP
2, 3
*BOUNDARY
FIX, 1, 3
TIP, 3, 3
*STEP
*STATIC
*NODE PRINT, NSET=TIP
U
*END STEP
*STEP
*STATIC
*DLOAD
1, BX, 2.
*NODE PRINT, NSET=TIP
U
*END STEP

How to run

OMP_NUM_THREADS=1 ccx shell_body
OMP_NUM_THREADS=1 ccx membrane_step

Expected

  • Element 3 (GRAV, the reference): force per unit area rho g t = 0.2, so q = 0.2 per unit length of the 1-wide strip. Beam theory gives w = q L^4 / (8 E I) = 0.2 / (8 x 1000 x 0.1^3 / 12) = 0.300; the one-element S4 gives U3 = 0.302.
  • Elements 1 and 2 (BZ = 2 per unit volume along global z, the same load as element 3): U3 = +0.302 at both tips. Node order must not change the result.
  • Element 4 (BX = 2, in plane): U1 = B L^2 / (2 E) = 1.0E-03, U3 = 0.
  • membrane_step, step 2: U1 = 1.000000E-03, which is what the same card gives in step 1.

Actual

Exit 0, no warning. shell_body.dat:

 displacements (vx,vy,vz) for set TIP and time  0.1000000E+01
         2  2.303713E-15  2.604738E-15  3.020000E+00
         3 -2.303713E-15  2.963482E-15  3.020000E+00
        12  4.718448E-16  1.346252E-15 -3.020000E+00
        13 -1.193490E-15  2.757810E-15 -3.020000E+00
        22 -1.092876E-16 -1.965190E-16  3.020000E-01
        23  2.532696E-16 -1.910079E-16  3.020000E-01
        32  7.716050E-15  9.392568E-15  3.020000E+00
        33 -6.522560E-15  9.146878E-15  3.020000E+00
element load expected tip actual tip
1 BZ 2 U3 = 0.302 U3 = 3.020 (10x = 1/t)
2 BZ 2, nodes reversed U3 = 0.302 U3 = -3.020 (opposite sense)
3 GRAV 2 along z U3 = 0.302 U3 = 0.302
4 BX 2 U1 = 1.0E-03, U3 = 0 U1 = 7.7E-15, U3 = 3.020

Element 1 moves exactly as under a pressure: with its line replaced by 1, P, 2. the tip U3 is the same 3.020000E+00. With element 4’s line changed to 4, BY, 2., its tip also moves U3 = 3.020000E+00.

membrane_step.dat, step 2: U1 = 7.105427E-21 and 1.980599E-34 at nodes 2 and 3 instead of 1.000000E-03. The body force became a pressure along z, which the z supports take.

Where it goes wrong

dloads.f accepts the label for every element type (master lines 322-334, (label(1:2).ne.'BX').and. ...). For a shell, both the single-element branch (lines 373-393) and the element-set branch (lines 495-515) then end in a catch-all:

            elseif((lakon(l)(1:1).eq.'S').or.
     &             (lakon(l)(7:7).eq.'L')) then
...
              else
                label(1:2)='P1'
              endif

The magnitude is passed on unchanged (xxmagnitude=xmagnitude), so BX/BY/BZ becomes P1, a pressure on face 1 of the expanded brick, along the shell normal. The catch-all fits the manual’s statement for shells (section 7.44), “there is only one kind of loading: pressure in the direction of the normal on the shell”, but BX/BY/BZ are volumetric loads (LOGBOOK, 4 April 2009), not pressures.

For solid elements e_c3d.f (master lines 1214-1251) integrates the label as a body force per unit volume:

                if(sideload(id)(1:2).eq.'BX')
     &               ff(jj1)=ff(jj1)+xload(1,id)*shpj(4,jj)*weight

The expanded shell is a brick of thickness t, so this path would give B t per unit area, which is what GRAV gives through the density.

Membranes are expanded by gen3delem.f with the same marker as shells (lakon(i)(1:7)='C3D8 L' for M3D4, line 364). Before that, in the first step, the lakon is M3D4 and BX stays a body force; in later steps it hits the shell branch.

Fix

Not tested (no build here):

  • In both shell branches of dloads.f, keep labels that start with B (BX, BY, BZ and their NU forms) instead of renaming them to P1, e.g. add elseif(label(1:1).eq.'B') then (no change) before the final else. e_c3d.f then integrates the body force over the expanded brick, as for solids.

  • Document BX, BY and BZ in section 7.44 (*DLOAD): unit (force per unit volume), the element types that take them, and what OP=NEW does with them. For axisymmetric elements the manual could also say that BZ (normal to the r-z plane) is not allowed: on one CAX4 ring (r 1 to 2, E = 1000, nu = 0, held axially at the bottom) EALL, BZ, 1. moves the nodes radially by -2.13E-05 / -2.24E-05, -1.0 % of what BX, 1. gives, with exit 0 and no message (not traced).

    cax_bz.inp
    *NODE
    1, 1., 0.
    2, 2., 0.
    3, 2., 1.
    4, 1., 1.
    *ELEMENT, TYPE=CAX4, ELSET=EALL
    1, 1, 2, 3, 4
    *NSET, NSET=BOT
    1, 2
    *NSET, NSET=ALLN
    1, 2, 3, 4
    *MATERIAL, NAME=M
    *ELASTIC
    1000., 0.
    *SOLID SECTION, ELSET=EALL, MATERIAL=M
    *BOUNDARY
    BOT, 2, 2
    *STEP
    *STATIC
    *DLOAD
    EALL, BZ, 1.
    *NODE PRINT, NSET=ALLN
    U
    *END STEP
    

    Result (exit 0): U1 = -2.134727E-05 at nodes 1 and 4 (r = 1), -2.242735E-05 at nodes 2 and 3 (r = 2), U2 = 0 at the held nodes. With EALL, BX, 1. instead: U1 = 2.118271E-03 and 2.225447E-03.

Confirmed (using official build of ccx 2.23 on Windows), needs a GitHub ticket.

shell_body.dat:

                        S T E P       1


                                INCREMENT     1


 displacements (vx,vy,vz) for set TIP and time  0.1000000E+01

         2 -5.551115E-16 -1.286554E-15  3.020000E+00
         3  8.604228E-16 -1.304615E-15  3.020000E+00
        12 -3.691492E-15 -4.270350E-15 -3.020000E+00
        13  3.719247E-15 -4.287499E-15 -3.020000E+00
        22 -1.734723E-17 -3.879595E-17  3.020000E-01
        23 -1.075529E-16 -1.450431E-17  3.020000E-01
        32  7.216450E-16  9.232392E-16  3.020000E+00
        33  0.000000E+00  9.067282E-16  3.020000E+00

membrane_step.dat:

                        S T E P       1


                                INCREMENT     1


 displacements (vx,vy,vz) for set TIP and time  0.1000000E+01

         2  0.000000E+00  0.000000E+00  0.000000E+00
         3  0.000000E+00  0.000000E+00  0.000000E+00

                        S T E P       2


                                INCREMENT     1


 displacements (vx,vy,vz) for set TIP and time  0.2000000E+01

         2 -1.451109E-32 -2.136439E-32  0.000000E+00
         3  2.901228E-32 -1.980716E-32  0.000000E+00