Rhino STEP export converts straight lines to curves

Hello Everyone,

The following step files are the same part. Now I pulled it into Rhino and just re-exported it. What I noticed is that what were lines in the SolidWorks export become curves (even though they are perfectly straight). Probably in most cases not a problem, but in this case it breaks the import in third-party software for sheet metal processing.

To my question. Is there an option to export it so that real lines stay lines in the step export?

RhinoPart.stp (33.6 KB)
SolidPart.step (26.6 KB)

Fixed it with a post-processor script, but I’d rather have it Rhinonative.

"""Export selected objects as a STEP file.

Run inside Rhino:  _-RunPythonScript "<path>/StepExport.py"

Exports via Rhino's STEP writer, then rewrites the file in place:
- degree-1 B_SPLINE_CURVE_WITH_KNOTS (2 ctrl pts) -> LINE
- TRIMMED_CURVE -> direct reference to basis curve

Targets importers that reject spline-encoded straight edges and trimmed-curve
wrappers. Requires "Split closed surfaces" OFF in the STEP export options.
"""
import re, math, io
import Rhino
import scriptcontext as sc


def fix_step(path):
    txt = io.open(path, "r", encoding="ascii", errors="ignore").read()

    recs, order = {}, []
    for m in re.finditer(r"#(\d+)=(.*?);\s*?\n", txt, re.S):
        recs[int(m.group(1))] = m.group(2).strip()
        order.append(int(m.group(1)))

    maxid = [max(recs)]

    def nextid():
        maxid[0] += 1
        return maxid[0]

    def cart(pid):
        m = re.search(r"CARTESIAN_POINT\('[^']*',\(([^)]*)\)\)", recs[pid])
        return [float(x) for x in m.group(1).split(",")]

    replaced, new_entities, redirect = 0, {}, {}

    for eid in order:
        body = recs[eid]
        if body.startswith("B_SPLINE_CURVE_WITH_KNOTS"):
            m = re.match(r"B_SPLINE_CURVE_WITH_KNOTS\('[^']*',(\d+),\(([^)]*)\)", body)
            if not m or m.group(1) != "1":
                continue
            pts = [int(x.strip()[1:]) for x in m.group(2).split(",")]
            if len(pts) != 2:
                continue
            p0, p1 = cart(pts[0]), cart(pts[1])
            d = [b - a for a, b in zip(p0, p1)]
            L = math.sqrt(sum(x * x for x in d))
            if L == 0:
                continue
            did = nextid()
            new_entities[did] = "DIRECTION('',(%s))" % ",".join(repr(x / L) for x in d)
            vid = nextid()
            new_entities[vid] = "VECTOR('',#%d,%r)" % (did, L)
            recs[eid] = "LINE('',#%d,#%d)" % (pts[0], vid)
            replaced += 1
        elif body.startswith("TRIMMED_CURVE"):
            m = re.match(r"TRIMMED_CURVE\(.?.?,#(\d+),", body)
            if m:
                redirect[eid] = int(m.group(1))

    refpat = re.compile(r"#(\d+)")

    def sub(m):
        i = int(m.group(1))
        return "#%d" % redirect[i] if i in redirect else m.group(0)

    for eid in order:
        if eid not in redirect and "#" in recs[eid]:
            recs[eid] = refpat.sub(sub, recs[eid])

    for eid in redirect:
        del recs[eid]

    head, tail = txt.split("DATA;", 1)
    _, endsec = tail.rsplit("ENDSEC;", 1)
    out = [head + "DATA;\n"]
    for eid in order:
        if eid in recs:
            out.append("#%d=%s;\n" % (eid, recs[eid]))
    for eid in sorted(new_entities):
        out.append("#%d=%s;\n" % (eid, new_entities[eid]))
    out.append("ENDSEC;" + endsec)
    io.open(path, "w", encoding="ascii", newline="\n").write(u"".join(out))
    return replaced, len(redirect)


def main():
    doc = sc.doc
    if doc.Objects.GetSelectedObjects(False, False) is None or \
       not any(True for _ in doc.Objects.GetSelectedObjects(False, False)):
        Rhino.RhinoApp.WriteLine("StepExport: select the object(s) first.")
        return

    fd = Rhino.UI.SaveFileDialog()
    fd.Filter = "STEP files (*.stp)|*.stp"
    fd.DefaultExt = "stp"
    fd.Title = "Export STEP"
    if not fd.ShowSaveDialog():
        return
    path = fd.FileName

    ok = Rhino.RhinoApp.RunScript('_-Export "%s" _Enter _Enter _Enter' % path, False)
    import os
    if not ok or not os.path.exists(path):
        Rhino.RhinoApp.WriteLine("StepExport: export failed.")
        return

    lines, trims = fix_step(path)
    Rhino.RhinoApp.WriteLine(
        "StepExport: %s  (%d splines -> LINE, %d trimmed curves unwrapped)"
        % (path, lines, trims))


main()

Best, and thanks in advance!
Felix

Hi Felix,

AFAIK there is no way to do this in Rhino: a line is only exported as a line if it standalone. Once a line is part of something else (e.g. an edge) Rhino exports it as a curve. That’s the way Rhino sees it internally.

I have seen it suggested that opening the Rhino file in Fusion and exporting a STEP from there can work better with sheet metal software, but I don’t have personal experience of this. Nevertheless, if you want to try it I attach a Fusion STEP export of your SolidWorks file.

FusionPart.step (26.5 KB)

Regards
Jeremy

Hey Jeremy,
Thank you for your reply.

Yes, that works perfectly. I also gave Onshape a try and it works fine as well, so far I’ve only encountered this behaviour with Rhino.

Since I’m trying to batch export sheet metal parts, going through Fusion would be less ideal, so I’ll stick to my little helper script.

Maybe something to consider as a STEP export option in the future?

Best

I’m sure you have tried IGS as alternative, just to see if the lines are preserved? Of course IGS is usually less desirable for surface / solids export.

Hey @cdordoni

No, I have not actually… but this is more because the other software that I need to import the part into is very limited regarding formats,.So STEP/STP is basically the only format they support.

DXF is not supported? … that could work if it is.

Only if it’s already unfolded and flat. But the idea is to export the parts in 3D generated by SheepMetal.

Then the actual manufacturer is unfolding it since they have the specification regarding K-Factor and so on.

@felix.brunold - Is the SolidPart.step file the original in your process?

I see your script goes back into the STEP file and searches for specific conditions. Do you tink this is a general purpose solution? Or is it something that needs to be used for specific models. For instance in this case you know this is a series of planar edge curves that can be reduced to straight line and arcs?

i understand what you are looing for and why it would benefit sheet metal style or any kind of plasma or waterjet cutter process.

Here is a random model processed by the script. Can you read these in and see if the results are what you might expect? Trying to determine what the downside to the secondary filter in your script.

In my testing they both seem to result in OK. But would like to see if the additional software you use could read it?

testglue_scripted.stp (2.3 MB)

Yes, the SolidPart came out of SolidWorks

I can just say for the use case of sheet metal design it worked across a couple of sheet metal parts of various complexity. Beyond that, I did not do deeper testing.

Maybe it could help for other CAM applications as well? But that’s just a guess from my side.

The part attached I will probably not be able to test since I need valid sheet metal parts with unified thickness. But I will take a look into some other CAM programs for laser cutting if I find similar behavior with the whole Curve/Line types.

Best, and thank you for looking into that!

Yes, CAM products will benefit also.

RH-97978 is fixed in Rhino BETA

@scottd, @brian, you’re amazing; I tested and worked on my setup.

I’ll forward the beta to some other users that might run into the same problems to test on their CAM systems.

Best Felix