Month: November 2025

  • MPM fracture CRAMP – Line crossing algorithm visualizer

    MPM fracture CRAMP – Line crossing algorithm visualizer

    Check my github: MPM Field Visualizer

    Line Crossing Algorithm:

    The most time consuming, new calculation required for CRAMP is the linecrossing calculation in Task 2. It is important for this calculation to be optimal and precise. The only numerical difficulty occurs when a node lies very close to the crack path. In some line-crossing algorithms, numerical round off in this situation could result in two particles on the same side of the crack being labeled as being on opposite sides. The complementary problem of when a material point lines very close to a crack path never occurs because the contact methods keep particles from reaching the crack path.

    A line-crossing algorithm in 2D based on signed areas of certain triangles solved the problem of nodes on cracks. For any three points, x1, x2, and x3, the signed area of the triangle with those vertexes is given by

    Area = x1(y2−y3)+x2(y3−y1)+x3(y1−y2) (33)

    This area is positive if the path from x1 to x3 is counter clockwise, negative if it is clockwise, and zero if the points are collinear. Using this signed area, the algorithm is as follows:

    Subtask 1:

    Before doing any calculations, determine if the rectangle defined by the particle (x1 = xp) and the node (x2 = xn) under consideration intersects the extent of the segment endpoints in the crack. If it does not, the line does not cross the crack and rest of the algorithm can be skipped for that crack.

    Subtask 2:

    For each crack segment with endpoints x3 and x4, calculate the sign of the areas of triangles (123), (124), (341), and (342) denoted as “+”, “−” or “0”.

    Subtask 3:

    The particle is above the crack if the signs are (−++−), (−++0), (0++0), or (−0+0). The first case is the most common; the other three correspond to the node being on the crack segment, on the start point of the crack segment, or on the end point of the crack segment, respectively. The cases where the material point is on the crack segment can be ignored. Similarly, the particle is below the crack if the signs are (+−−+), (+−−0), (0−−0), or (+0−0). All other combinations of signs indicate the line does not cross the line segment. In practice, many signed area calculations can be skipped. For example is the signs of (123) and (124) are (++), there is no need to evaluate the signs of (341) and (342) because the line can not cross the segment.

    Subtask 4:

    One complication is that a given xp to xn line might cross more than one segment in a single crack. In this situation, the crossing is ignored unless there are an odd number of crossings. To include this possibility, the previous two steps must check all segments in a crack before deciding if there is a crossing, but if Subtask 1 finds no intersection, the check for all segments in that crack can be skipped.

    — Nairn (2003)

    Particle-crack posision check:

    Node-crack stability check. (Sub stack 4)

    <p id="<reference>Nairn-JA.-Material-point-method-calculations-with-explicit-cracks.-Comput-Model-Eng-Sci-2003;4:649–64.References:

    Nairn JA. Material point method calculations with explicit cracks. Comput Model Eng Sci 2003;4:649–64.

    Tito Adibaskoro, Stéphane Bordas, Wojciech T. Sołowski, Simo Hostikka,
    Multiple discrete crack initiation and propagation in Material Point Method, Engineering Fracture Mechanics, Volume 301, 2024, 109918, ISSN 0013-7944, https://doi.org/10.1016/j.engfracmech.2024.109918.

  • Create equation caption and referencing MSWord

    Create equation caption and referencing MSWord

    1. Manual

    Alt + = to trigger Equation Editor.

    Default Unicode mode.

    Type

    “<<Equation>>(#4.1)”

    Enter

    Result

    *Note: No cross-referencing available!!!

    2. Automatic and cross-referencing enable

    2.1. Table way

    • Insert 3 column table
    • Format it to remove all margin and no border. Center align the middle cell, left align the left and right align the right
    • Insert an example equation in middle cell and a citation in right cell
    • Select whole table > Insert > On the Insert tab, in the Text group, click Quick Parts, and then click Save Selection to Quick Part Gallery, change the name and add a description if you like, and click OK.
    • Assign a keyboard shortcut to the Quick Part via File > Options > Customize Ribbon > Customize. Choose the “Building Blocks” category in the dialog box and locate your entry. Use the “Press new shortcut key” box to assign the shortcut (e.g. Alt + Ctrl + =), and then click the Assign button.

    2.2. Style separator way

    • <Alt + => and type the equation
    • Make it right align or pre-define a Styles that right-align.
    • Enter the next line and Insert caption (<Alt><S><P>)
    • Place the cursor right at the end of equation and insert a Style separator <Ctrl+Alt+Enter>
    • Manually adjust the spacing for sophisticated purpose.

    2.3. Cross-referencing

    <Alt><S>(quick:<R><F>) -> Equation…

  • Custom Python Scripts for AutoCAD Plant 3D – Case study of tubing fittings – SCRIPT

    from varmain.primitiv import *
    from varmain.custom import *
     
    @activate(Group="Coupling", TooltipShort="Tube Male Connector TubexMPT", TooltipLong="Tube Male Connector TubexMPT", FirstPortEndtypes="P", LengthUnit="in",  Ports="2")
    @group("MainDimensions")
    @param(D=LENGTH, TooltipShort="Tube OD", TooltipLong="Tube OD")
    @param(D1=LENGTH, TooltipShort="Hex head OD", TooltipLong="Fitting hex head OD")
    @param(D31=LENGTH, TooltipShort="Hex OD of Body", TooltipLong="Hex OD of Body")
    @param(L=LENGTH, TooltipShort="Length of Fitting", TooltipLong="Overall Length of Fitting")
    @param(L31=LENGTH, TooltipShort="Tube gland", TooltipLong="Tube gland length")
    @param(I1=LENGTH, TooltipShort="Tube insert", TooltipLong="Tube insert distance")
    @param(OF=LENGTH0)
     
    #(arxload "PnP3dACPAdapter")
    #(testacpscript "maleConnector")
    def maleConnector(s, D=0.25, D1=0.5625, D31=1.0625, L=1.82, L31=0.6, I1=0, K=1, OF=0, **kw):
        #length
        L32 = L31*0.6 #adjusting the length of hex head
        L33 = L - L31 #adjusting the length of hex head
        L1 = 0.2 #adjusting the length of body
           
        #hex head
        headStartangle = 0
        headBox1 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle) #cos(30deg) = 0.866
        headBox2 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+60) #cos(30deg) = 0.866
        headBox3 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+2*60) #cos(30deg) = 0.866
        headBox4 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+3*60) #cos(30deg) = 0.866
        headBox5 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+4*60) #cos(30deg) = 0.866
        headBox6 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+5*60) #cos(30deg) = 0.866
        headBox1.uniteWith(headBox2)
        headBox1.uniteWith(headBox3)
        headBox1.uniteWith(headBox4)
        headBox1.uniteWith(headBox5)
        headBox1.uniteWith(headBox6)
        headBox2.erase()
        headBox3.erase()
        headBox4.erase()
        headBox5.erase()
        headBox6.erase()
    
        head1 = CYLINDER(s, R=D1/2, H=L32, O=0).rotateY(90)
        head1.subtractFrom(headBox1)
        headBox1.erase()
    
        #body create
        bodyMain = CYLINDER(s, R=0.8*D1/2, H=L, O=0).rotateY(90)
    
        # hex head second
        head2Startangle = 0
        head2Box1 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle) #cos(30deg) = 0.866
        head2Box2 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle+60) #cos(30deg) = 0.866
        head2Box3 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle+2*60) #cos(30deg) = 0.866
        head2Box4 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle+3*60) #cos(30deg) = 0.866
        head2Box5 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle+4*60) #cos(30deg) = 0.866
        head2Box6 = BOX(s, L=D31, W=D31, H=L33*0.2).translate((L33*0.2/2+L31,0,1.866*D31/2)).rotateX(head2Startangle+5*60) #cos(30deg) = 0.866
        head2Box1.uniteWith(head2Box2)
        head2Box1.uniteWith(head2Box3)
        head2Box1.uniteWith(head2Box4)
        head2Box1.uniteWith(head2Box5)
        head2Box1.uniteWith(head2Box6)
        head2Box2.erase()
        head2Box3.erase()
        head2Box4.erase()
        head2Box5.erase()
        head2Box6.erase()
    
        head2 = CYLINDER(s, R=D31/2, H=L33*0.2, O=0).rotateY(90).translate((L31,0,0))
        head2.subtractFrom(head2Box1)
        head2Box1.erase()
    
        head2F = CONE(s, R1=D31/2*0.85, R2=D31/2*0.7, H=L33, E=0).rotateY(90).translate((L31,0,0))
        head2.uniteWith(head2F)
        head2F.erase()
    
        #joint 3 parts
        bodyMain.uniteWith(head1)
        bodyMain.uniteWith(head2)
        head1.erase()
        head2.erase()
    
        #create a hole fitting
        boreCy = CYLINDER(s, R=D/2, H=L, O=0).rotateY(90)
        bodyMain.subtractFrom(boreCy)
        boreCy.erase()
    
        #tube insert distance
        if I1 == 0:
            I1 = L31
        
        #set port points
        s.setPoint((0.0 + I1, 0.0, 0.0), (-1.0, 0.0, 0.0), 0)
        s.setPoint((L, 0.0, 0.0), (1.0, 0.0, 0.0), 0)
        
        return
    
    

    See PART 1:

    https://enginine.com/2025/11/11/custom-python-scripts-for-autocad-plant-3d-case-study-of-tubing-fittings-part-1/

  • Custom Python Scripts for AutoCAD Plant 3D – Case study of tubing fittings – PART 2

    After testing the script in PART 1, a geometry is created. Our job now is now publish it into the catalog.

    Image creation

    Manually dim all the necessary and changing CAD environment into white background (or any color)

    Capture the image using an app like PicPick or Cropper to create three images with different sizes: maleConnector_64.png maleConnector_200.png maleConnector_640.png with size 64×64, 200×200, 640×640 accordingly

    PART CREATION

    Final step is to launch Spec Editor. Open an existing CustomCatalog (better than a whole new catalog using CatalogBuilder). Choose “Create New Component” and choosing our part by selecting the group (defined in script).

    Setting and save the catalog. Now it ready-to-use in Plant3D.

    See PART 3:

    https://enginine.com/2025/11/11/custom-python-scripts-for-autocad-plant-3d-case-study-of-tubing-fittings-script/

  • Custom Python Scripts for AutoCAD Plant 3D – Case study of tubing fittings – PART 1

    Let’s try a simple male connector. First, we’ll grasp a drawing from Swagelok as reference:

    Coding

    We import libraris in the header, then define part’s principal dimensions which used to drive the entire model, serving as user inputs:

    from varmain.primitiv import *
    from varmain.custom import *
     
    @activate(Group="Coupling", TooltipShort="Tube Male Connector TubexMPT", TooltipLong="Tube Male Connector TubexMPT", FirstPortEndtypes="P", LengthUnit="in",  Ports="2")
    @group("MainDimensions")
    @param(D=LENGTH, TooltipShort="Tube OD", TooltipLong="Tube OD")
    @param(D1=LENGTH, TooltipShort="Hex head OD", TooltipLong="Fitting hex head OD")
    @param(D31=LENGTH, TooltipShort="Hex OD of Body", TooltipLong="Hex OD of Body")
    @param(L=LENGTH, TooltipShort="Length of Fitting", TooltipLong="Overall Length of Fitting")
    @param(L31=LENGTH, TooltipShort="Tube gland", TooltipLong="Tube gland length")
    @param(I1=LENGTH, TooltipShort="Tube insert", TooltipLong="Tube insert distance")
    @param(OF=LENGTH0)
    

    Define the function:

    def maleConnector(s, D=0.25, D1=0.5625, D31=1.0625, L=1.82, L31=0.6, I1=0, K=1, OF=0, **kw):
    

    As AutoCAD Plant 3D (PLNT3D) only accept a few primitives, we have to being creative in our coding part.

    Hex nut shape will be treat as a cylinder being trimmed by six cubes, then delete these cubes away:

        #hex head
        headStartangle = 0
        headBox1 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle) #cos(30deg) = 0.866
        headBox2 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+60) #cos(30deg) = 0.866
        headBox3 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+2*60) #cos(30deg) = 0.866
        headBox4 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+3*60) #cos(30deg) = 0.866
        headBox5 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+4*60) #cos(30deg) = 0.866
        headBox6 = BOX(s, L=D1, W=D1, H=L32).translate((L32/2,0,1.866*D1/2)).rotateX(headStartangle+5*60) #cos(30deg) = 0.866
        headBox1.uniteWith(headBox2)
        headBox1.uniteWith(headBox3)
        headBox1.uniteWith(headBox4)
        headBox1.uniteWith(headBox5)
        headBox1.uniteWith(headBox6)
        headBox2.erase()
        headBox3.erase()
        headBox4.erase()
        headBox5.erase()
        headBox6.erase()
    
        head1 = CYLINDER(s, R=D1/2, H=L32, O=0).rotateY(90)
        head1.subtractFrom(headBox1)
        headBox1.erase()
    

    After create other shapes, we combine them into one body:

        #joint 3 parts
        bodyMain.uniteWith(head1)
        bodyMain.uniteWith(head2)
        head1.erase()
        head2.erase()
    

    Create insert point and port points. As tube will penetrate these fittings in tubing port, we have to offset one port inwardy:

        #tube insert distance
        if I1 == 0:
            I1 = L31
        
        #set port points
        s.setPoint((0.0 + I1, 0.0, 0.0), (-1.0, 0.0, 0.0), 0)
        s.setPoint((L, 0.0, 0.0), (1.0, 0.0, 0.0), 0)
    

    Code Testing

    Choose Share Content folder by command MODIFYSHAREDCONTENTFOLDER or use default location (C:\AutoCAD Plant 3D 2016 Content\CPak Common\CustomScripts). Save script file at that location.

    Command PLANTREGISTERCUSTOMSCRIPTS to compiled the script. Load PnP3dACPAdapter.arx by arxload "PnP3dACPAdapter".

    Test the script by testacpscript "maleConnector".

    Note: If any modification, PLNT3D must be closed, then register script again.

    See PART 2: