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()