[Solved] How to obtain a sectioning plane orthogonal to a line.

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

[Solved] How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

The problem is:

I have a generic segment and I want to create a "sectioning plane" orthogonal to this segment (at a given point) to make a common operation with a shape and obtain a wire of the section.

So basically I have the line and the point on the line where I have to place such Plane.

I have a bunch of lines and some points see:
inters_f2.png
inters_f2.png (5.81 KiB) Viewed 2035 times
basically I have a line and a point:

Code: Select all

v1 = Vector (0.0, 0.0, 40.0)
v2 =  Vector (58.951034882180956, 191.11456115722808, 40.0)

# point on line where I have to place the sectioning plane 
v3 = Vector (40.07058702976817, 129.9056525608417, 40.0)

line = Part.LineSegment(v1, v2)
How to make this plane in an "elegant way".

NOTICE:

This post is finalized to advance this scripting example:

https://forum.freecadweb.org/viewtopic.php?f=22&t=65308

and is a consequence of this other post:

https://forum.freecadweb.org/viewtopic.php?f=22&t=65722

I whish to thanks @Chris_G and @edwilliams16 for the invaluable help and patience.


All this work has a goal to propose a "real world" scripting example, maybe it will be a wiki page or a different thing, so I tank in advance all the posters, and I will cite them in the final work. (You are working for the glory :D)


Regards

Carlo D.
Last edited by onekk on Sun Jan 30, 2022 12:14 pm, edited 1 time in total.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2579
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by Chris_G »

Code: Select all

v1 = Vector (0.0, 0.0, 40.0)
v2 =  Vector (58.951034882180956, 191.11456115722808, 40.0)

# point on line where I have to place the sectioning plane 
v3 = Vector (40.07058702976817, 129.9056525608417, 40.0)

line = Part.LineSegment(v1, v2)
plane = Part.Plane(v3, v2-v1)  # Part.Plane(Location,Normal)
If you need to use the plane for boolean operation, you can convert it to shape.

Code: Select all

plane_shape = plane.toShape()
This will be an infinite shape. I think this is not a problem as long as you don't try to display it.
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by edwilliams16 »

Code: Select all

v1 = Vector (0.0, 0.0, 40.0)
v2 =  Vector (58.951034882180956, 191.11456115722808, 40.0)

# point on line where I have to place the sectioning plane
v3 = Vector (40.07058702976817, 129.9056525608417, 40.0)

line = Part.LineSegment(v1, v2)
Part.Show(line.toShape())
length = 500
plane = Part.makePlane(length, length, v3, (v2 -v1).normalize())#through v3 normal to v2-v1
d1 = plane.derivative1At(0,0)
tr = -(d1[0]+d1[1])*length/2
plane.translate(tr)# center on v3
Part.show(plane)
For a finite plane, centered on v3
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

Thanks to all for solutions.

@Chris_G you solution is elegant, and could be used with some caveats as I have to taylor some of the actual code to not intersect with an infinite plane many lines (in the real example sprockets).

#edwilliams16 (sorry if sometimes I will write you nick wrong :) ) you solution is also good and makePlane will permit to use it in the code slightly easily.

Many thanks again and Regards

Carlo D.

PS: I will mark as Solved as far I will test thing and make some images to proper illustrate the thing, as I think that this thread will be helpful for others user.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

edwilliams16 wrote: Thu Jan 27, 2022 10:47 pm For a finite plane, centered on v3
Hello, obviously my vector math is not correct, so I could not catch the positioning on the plane with different X and Z coordinate (I know that is not correct, but have mercy, my math terminology is at primary school level, at least in English) so I'm obtaining this:
inter_f3.png
inter_f3.png (6.44 KiB) Viewed 1841 times
inter_f4.png
inter_f4.png (6.98 KiB) Viewed 1841 times
using this code:

Code: Select all

    spk_zw = 30
    spk_zh = 100
    edges = []
    for l_idx, line in enumerate(s_lines):
        Part.show(line.toShape(), "line{}".format(l_idx))
        d, ipts, info = line.toShape().distToShape(shell)
        pts_on_lines, pts_on_shell = list(zip(*ipts))

        v3 = pts_on_lines[0]
        print("v3 = ", v3)
        ln_n = (line.EndPoint - line.StartPoint).normalize()
        plane = Part.makePlane(spk_zw, spk_zh, v3, ln_n)
        d1 = plane.derivative1At(0, 0)
        tr = -(d1[0] + d1[1]) * spk_zw * 0.5
        plane.translate(tr)

        Part.show(plane, "plane{}".format(l_idx))
In real code spk_zw and spk_zh are derived from the bounding box of the real shape.

Another minor problem is that plane are intersecting the cut shape, but this is not as asked in the "Help Question" to not pollute this thread, I will make another post maybe in the original Thread about modelling the "Engine Wheel".

I whish to make the problem in "small pieces" and maybe ask for every problem I encounter a different post that maybe will be more helpful that a 100 pages thread.

As the intent is to be a teaching example, I will ask your help even to make a proper "post title" to be helpful even in future, or maybe make every little steps a more reasoned example. (In the perspective of a wiki page).

EDIT: at the very end, i don't catch the u,v parameters meaning, I've searched the web but the only thing I've found is the UV space related to texture mapping, and some Blender references. (There is some place how I could study some basics, so I could catch the math behind and don't thing it is some "black magic".)

Best Regards

Carlo D.
20220127-intersect.py
(4.71 KiB) Downloaded 27 times
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

onekk wrote: Fri Jan 28, 2022 8:37 am EDIT: at the very end, i don't catch the u,v parameters meaning, I've searched the web but the only thing I've found is the UV space related to texture mapping, and some Blender references. (There is some place how I could study some basics, so I could catch the math behind and don't thing it is some "black magic".)
Ok catched the "black magic", or maybe not.

I've found some explanation that resemble what I need in:

https://dev.opencascade.org/doc/occt-7. ... orial.html

under:

Defining 2D Curves


Where it describe the making of the Thread of the "OCC Bottle"

So it is simple a parametric way to describe the "surface" as mapped on a "cartesian plane"?

My guess is right or I have missed something, for a plane is simple to make such a representation, but for another surface?


Now I don't catch how the placement code:

Code: Select all

d1 = plane.derivative1At(0,0)
tr = -(d1[0]+d1[1])*length/2
plane.translate(tr)# center on v3
Has to be modified to correctly place the plane at his center with the correct orientation.

Maybe another post will be a more "educational" way of being useful to others?

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2579
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by Chris_G »

Hello Carlo,
Here is how I would deal with the orientation problem.

A Part.Plane can be created from a point and a normal, but then, you don't have control over the rotation of the plane around normal.
On the other hand, we can create a fully oriented plane, from 3 points :

Code: Select all

def build_plane(center, normal, localY):
    """create a plane from center, normal and local Y axis"""
    cross = normal.cross(localY)
    return Part.Plane(center, center + cross, center + localY)
Then you can build a face from 2D geometries, on that plane.
For example, a rectangle :

Code: Select all

def rectangle_on_plane(width, height, plane):
    """Returns a rectangular face of size width x height centered on plane"""
    vec2 = FreeCAD.Base.Vector2d
    w = width / 2
    h = height / 2
    top = Part.Geom2d.Line2dSegment(vec2(-w, h), vec2(w, h))
    right = Part.Geom2d.Line2dSegment(vec2(w, h), vec2(w, -h))
    bottom = Part.Geom2d.Line2dSegment(vec2(w, -h), vec2(-w, -h))
    left = Part.Geom2d.Line2dSegment(vec2(-w, -h), vec2(-w, h))
    edges = [line.toShape(plane) for line in (top, right, bottom, left)]
    return Part.Face(plane, Part.Wire(edges))
or a circle :

Code: Select all

def circle_on_plane(radius, plane):
    """Returns a circular face of given radius centered on plane"""
    circ2d = Part.Geom2d.Circle2d()
    circ2d.Radius = radius
    circ3d = circ2d.toShape(plane)
    return Part.Face(plane, Part.Wire([circ3d]))
Here is the full code :

Code: Select all

"""Sample code.

This code was written as an sample code

Name: filename.py

Author: Carlo Dormeletti
Copyright: 2022
Licence: CC BY-NC-ND 4.0 IT
"""
import os # noqa
import sys  # noqa
import math # noqa
from math import cos, pi, sin, sqrt  # noqa

import FreeCAD
import FreeCADGui # noqa
from FreeCAD import Placement, Rotation, Vector # noqa
import Part # noqa

DOC = FreeCAD.activeDocument()

DOC_NAME = "test_offset"


def clear_doc():
    """Clear active document deleting all the objects."""
    for obj in DOC.Objects:
        DOC.removeObject(obj.Name)


def setview():
    """Rearrange View."""
    FreeCAD.Gui.SendMsgToActiveView("ViewFit")
    FreeCAD.Gui.activeDocument().activeView().viewAxometric()


if DOC is None:
    FreeCAD.newDocument(DOC_NAME)
    FreeCAD.setActiveDocument(DOC_NAME)
    DOC = FreeCAD.activeDocument()

else:

    clear_doc()

# EPS= tolerance to use to cut the parts
EPS = 0.10
EPS_C = EPS * -0.5
ROT0 = Rotation(0, 0, 0)

# CODE START HERE


def tangent2C(c1, c2, rad1, rad2):
    """Calculate four tangent point to join two circles.

    Courtesy of edwilliams16

    Parameters:
    c1    Vector  Center of circle1
    c2    Vector  Center of circle2
    rad1  float   Radius of circle1
    rad2  float   Radius of circle2

    Return:
    [tg1, tg2, tg3, tg4] tangent points.
    If one circle strictly encloses the other or are they are concentric
    return []
    """
    lg = (c1 - c2).Length

    if abs(rad1 - rad2) > lg or lg == 0:
        return []
    else:
        c = (rad1 - rad2) / lg
        s = sqrt(1 - c * c)
        n = (c2 - c1) / lg  # unit vector along line of centers
        m = Vector(0, 0, 1).cross(n)  # orthogonal vector
        vp = c * n + s * m  # unit vectors from center to tangent points
        vm = c * n - s * m
        return [c1 + rad1 * vp, c1 + rad1 * vm, c2 + rad2 * vp, c2 + rad2 * vm]


def dest_pt(pt, angle, length):
    """Calculate destination point.

    Vector math courtesy of edwilliams16

    Parameters:
      pt    = starting point
      angle =  rad
      length = units
    """
    # return Vector(pt.x + math.cos(angle) * length, pt.y + math.sin(angle) * length, pt.z)

    lv = Vector(length, 0, 0)
    p_rot = Rotation(math.degrees(angle), 0, 0)
    dept = Placement(pt, p_rot).multVec(lv)

    return dept


def mk_shell():
    """Make an shell for test."""
    c1cen = Vector(0, 0, 0)
    c1rad = 100
    c2cen = Vector(0, 100, 0)
    c2rad = 50

    tgp1, tgp2, tgp3, tgp4 = tangent2C(c1cen, c2cen, c1rad, c2rad)

    cir1 = Part.makeCircle(c1rad, c1cen)
    cir2 = Part.makeCircle(c2rad, c2cen)
    edg1 = Part.LineSegment(tgp3, tgp1).toShape()
    edg2 = Part.LineSegment(tgp2, tgp4).toShape()

    # obtain cir1 section between tangents
    par1 = cir1.Curve.parameter(tgp1)
    par2 = cir1.Curve.parameter(tgp2)

    tgv1 = cir1.Curve.value(par1)
    tgv2 = cir1.Curve.value(par2)

    c1_3pos = tgv1 - (tgv2 - tgv1) * 0.5
    par3 = cir1.Curve.parameter(c1_3pos)

    tgv3 = cir1.Curve.value(par3)

    edg3 = Part.ArcOfCircle(tgv2, tgv3, tgv1).toShape()

    # obtain cir2 section between tangents
    par4 = cir2.Curve.parameter(tgp3)
    par5 = cir2.Curve.parameter(tgp4)

    tgv4 = cir2.Curve.value(par4)
    tgv5 = cir2.Curve.value(par5)

    c2_3pos = tgv4 + (tgv5 - tgv4) * 0.5
    par6 = cir2.Curve.parameter(c2_3pos)

    tgv6 = cir2.Curve.value(par6)

    edg4 = Part.ArcOfCircle(tgv4, tgv6, tgv5).toShape()

    # edge are ordered in counterclockwise
    obj_f = Part.Wire((edg2, edg3, edg1, edg4))

    # Part.show(obj_f, "wire")

    return obj_f


def rectangle_on_plane(width, height, plane):
    """Returns a rectangular face of size width x height centered on plane"""
    vec2 = FreeCAD.Base.Vector2d
    w = width / 2
    h = height / 2
    top = Part.Geom2d.Line2dSegment(vec2(-w, h), vec2(w, h))
    right = Part.Geom2d.Line2dSegment(vec2(w, h), vec2(w, -h))
    bottom = Part.Geom2d.Line2dSegment(vec2(w, -h), vec2(-w, -h))
    left = Part.Geom2d.Line2dSegment(vec2(-w, -h), vec2(-w, h))
    edges = [line.toShape(plane) for line in (top, right, bottom, left)]
    return Part.Face(plane, Part.Wire(edges))

def circle_on_plane(radius, plane):
    """Returns a circular face of given radius centered on plane"""
    circ2d = Part.Geom2d.Circle2d()
    circ2d.Radius = radius
    circ3d = circ2d.toShape(plane)
    return Part.Face(plane, Part.Wire([circ3d]))

def build_plane(center, normal, localY):
    """create a plane from center, normal and local Y axis"""
    cross = normal.cross(localY)
    return Part.Plane(center, center + cross, center + localY)

def find_inters():
    """Find intersection between lines and a shell."""
    n_sp = 21
    ang_adv = 360 / n_sp
    ln_lg = 200
    sh_thi = 80

    s_lines = []
    for idx in range(0, n_sp):
        end_pt = dest_pt(
            Vector(0, 0, 0),
            pi / 2.0 + math.radians(ang_adv * idx),
            ln_lg)
        line = Part.LineSegment(
            Vector(0, 0, sh_thi * 0.5),
            Vector(end_pt.x, end_pt.y, sh_thi * 0.5))
        s_lines.append(line)
        # Part.show(line, "line{}".format(idx))

    wire = mk_shell()
    shell = wire.extrude(Vector(0, 0, sh_thi))
    Part.show(shell, "shell")

    spk_zw = 30
    spk_zh = 100
    edges = []
    for l_idx, line in enumerate(s_lines):
        Part.show(line.toShape(), "line{}".format(l_idx))
        d, ipts, info = line.toShape().distToShape(shell)

        line_dir = line.tangent(line.FirstParameter)[0]
        plane = build_plane(ipts[0][0], line_dir, Vector(0, 0, 1))

        face = rectangle_on_plane(spk_zw, spk_zh, plane)
        # face = circle_on_plane(spk_zw, plane)

        Part.show(face, "plane{}".format(l_idx))

find_inters()

setview()
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

Chris_G wrote: Sat Jan 29, 2022 11:05 am Hello Carlo,
Here is how I would deal with the orientation problem.

WOW, many Thanks.

I have to study your code, as You have used some new (for me functions)

All these reference to Part.Geom2d. are new for me at least in FC.

As a side note I'm struggling to make another thing, (as an exercise using the code on OpenCascade Tutorial about the OCC Bottle neck thread, but FC lacks of some things, so I have read some OCCT documentation and encountered Geom2D API, sadly not everything is exposed by FC.

But this maybe could be a subject of another thread, (If you kindly will participate to this future thread I will be pleased) as the whole part is omitted from the wiki post:

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6144
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by onekk »

Chris_G wrote: Sat Jan 29, 2022 11:05 am Hello Carlo,
Here is how I would deal with the orientation problem.
Sorry for poking, and abusing of your kindness.

The U,V parameters, are resemblig the explanation made in:

https://dev.opencascade.org/doc/occt-7. ... orial.html
You can consider a surface as a 2D parametric space defined with a (U, V) coordinate system.
in these images.
tutorial_image012.png
tutorial_image012.png (7.81 KiB) Viewed 1653 times
tutorial_image013.png
tutorial_image013.png (4.29 KiB) Viewed 1653 times
tutorial_image014.png
tutorial_image014.png (4.97 KiB) Viewed 1653 times

TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2579
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: How to obtain a sectioning plane orthogonal to a line.

Post by Chris_G »

I think Geom2d is very useful.
It is like drawing on a stretchable sticker, that you can then apply on any 3D surface you want:

Code: Select all

# a 2D geometry
circ2d = Part.Geom2d.Circle2d()

# some 3D surfaces
bs = Part.BSplineSurface()
bs.setUKnots([-2, 2])
bs.setVKnots([-2, 2])
sphere = Part.Sphere()
torus = Part.Toroid()

# apply the 2D geometry on the 3D surfaces
for surf in (bs, sphere, torus):
    Part.show(surf.toShape(), "surf")
    Part.show(circ2d.toShape(surf), "circle2d_on_surf")

Post Reply