Wrapping a sketch around a cylinder

Have some feature requests, feedback, cool stuff to share, or want to know where FreeCAD is going? This is the place.
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
Allcome
Posts: 2
Joined: Sat Mar 12, 2016 6:10 pm

Wrapping a sketch around a cylinder

Post by Allcome »

I've searched the web and these forums, and if there's an easy way to wrap a sketch around a cylinder, it has eluded me.

I have attached a simple script that wraps a simple sketch (only straight lines) around a cylinder, and then lofts it to a specified thickness.

However, this technique seems to fail when the wrapping is larger than pi (180 degrees). Is this an inherent limitation to Part.makeFilledFace?

Is there a better way to wrap a sketch around a cylinder (preferably one that allows wraps greater than 180 degrees, and also allows curves to be mapped)?


Thanks!

Code: Select all

from __future__ import division # allows floating point division from integers
import FreeCAD, FreeCADGui
from FreeCAD import Base
import Part
import math

def GetObjectByLabel(objectName):
  for oo in FreeCAD.ActiveDocument.Objects:
    if oo.Label == objectName:
      return oo
  return None

def getPointOnCylinder(ax, ay, Radius):
  angle = ax/Radius
  result = (math.cos(angle)*Radius, math.sin(angle)*Radius, ay)
  return result

def WrapSketch(Sketch, Radius, XScale):
  Edges = []
  for gg in Sketch.Geometry:
    if str(type(gg)) == "<type 'Part.GeomLineSegment'>":
      if (gg.Construction):
        continue
      sp = gg.StartPoint
      ep = gg.EndPoint
      if (sp.x > ep.x):
        tt = sp
        sp = ep
        ep = tt

      sp.x = sp.x*XScale
      ep.x = ep.x*XScale
      if (sp.x == ep.x):
        Edges.append(Part.makeLine(getPointOnCylinder(sp.x, sp.y, Radius), getPointOnCylinder(ep.x, ep.y, Radius)))
      elif (sp.y == ep.y):
        Angle0 = sp.x/Radius*180/math.pi
        Angle1 = ep.x/Radius*180/math.pi
        cc = Part.makeCircle(Radius, Base.Vector(0, 0, gg.StartPoint.y), Base.Vector(0, 0, 1), Angle0, Angle1)
        Edges.append(cc)
      else:
        hh = Part.makeHelix(2*abs(ep.y - sp.y)*math.pi/(abs(ep.x - sp.x)/Radius), abs(ep.y - sp.y), Radius, 0, (sp.y > ep.y))
        if (sp.y > ep.y):
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), ep.x/Radius*180/math.pi);
        else:
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), sp.x/Radius*180/math.pi);
        hh.Placement.Base = Base.Vector(0, 0, min(sp.y, ep.y))
        Edges.append(hh)
  return Edges


class WrappedSketch:
  def __init__(self, obj, SketchLabel = "", RuledRadius = 20, Thickness = 2):

    PropertyGroup = "WrappedSketch";
    obj.addProperty("App::PropertyString", "SketchLabel", PropertyGroup, "").SketchLabel = SketchLabel

    obj.addProperty("App::PropertyFloat", "RuledRadius", PropertyGroup, "Radius for outer surface").RuledRadius = RuledRadius
    obj.addProperty("App::PropertyFloat", "Thickness", PropertyGroup, "Thickness of extrusion").Thickness = Thickness
    obj.Proxy = self

  def execute(self, obj):
    Sketch = GetObjectByLabel(obj.SketchLabel)
    if (Sketch == None):
      return

    InnerRadius = obj.RuledRadius - obj.Thickness

    Edges = WrapSketch(Sketch, obj.RuledRadius, 1.0)
    Shape = Part.makeCompound(Edges)
    Face1 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    Edges = WrapSketch(Sketch, InnerRadius, InnerRadius/obj.RuledRadius)
    Shape = Part.makeCompound(Edges)
    Face2 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    loft = Part.makeLoft([Face1.OuterWire, Face2.OuterWire], True, True);
    obj.Shape = loft.removeSplitter()


def makeWrappedSketch():
  if FreeCAD.ActiveDocument is None:
    return

  aSketchLabel = ""
  selections=FreeCAD.Gui.Selection.getSelectionEx()
  if len(selections) > 0:
    selObject = selections[0]
    if str(type(selObject.Object)) == "<type 'Sketcher.SketchObject'>":
      aSketchLabel = selObject.Object.Label

  a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","WrappedSketch")
  WrappedSketch(a, SketchLabel=aSketchLabel)
  a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
  FreeCAD.ActiveDocument.recompute()
  FreeCADGui.ActiveDocument.ActiveView.fitAll()
  return a
FC01.JPG
FC01.JPG (27.86 KiB) Viewed 13246 times
FC02.JPG
FC02.JPG (38.34 KiB) Viewed 13246 times
FC03.JPG
FC03.JPG (42.05 KiB) Viewed 13246 times
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Wrapping a sketch around a cylinder

Post by DeepSOIC »

No, there is no way to wrap a sketch around a cylinder, so your marco is very welcome!

I think there was another thread on this quite some time ago, but I can't find it.
JMG
Posts: 287
Joined: Wed Dec 25, 2013 9:32 am
Location: Spain
Contact:

Re: Wrapping a sketch around a cylinder

Post by JMG »

Hi!
I think there was another thread on this quite some time ago, but I can't find it.
viewtopic.php?t=10617 maybe?

That was something I did in the past. I also faced the same problem with lengths going further than 180 degrees.
Your script seems less complicated than mine (I had to use quaternions to transform from flat coordinates to polar).
Maybe its time to work on it again :)

Javier.
FreeCAD scripts, animations, experiments and more: http://linuxforanengineer.blogspot.com.es/
Open source CNC hot wire cutter project (NiCr): https://github.com/JMG1/NiCr
Exploded Assembly Workbench: https://github.com/JMG1/ExplodedAssembly
User avatar
DeepSOIC
Veteran
Posts: 7896
Joined: Fri Aug 29, 2014 12:45 am
Location: used to be Saint-Petersburg, Russia

Re: Wrapping a sketch around a cylinder

Post by DeepSOIC »

JMG wrote:viewtopic.php?t=10617 maybe?
exactly!
Allcome
Posts: 2
Joined: Sat Mar 12, 2016 6:10 pm

Re: Wrapping a sketch around a cylinder

Post by Allcome »

JMG wrote: Maybe its time to work on it again :)

Javier.
If you decide to take this up again, please investigate wrapping curves, as well. I experimented with converting the arcs to bsplines, and then mapping the poles of the bsplines onto the cylinder. However, I'm not comfortable with bsplines, and I'm not confident that the result is mathematically correct. (My cursory testing [sample size = 2] seems to look okay! ;) )

Here's a stab at including arcs:

Code: Select all

from __future__ import division # allows floating point division from integers
import FreeCAD, FreeCADGui
from FreeCAD import Base
import Part
import math

def GetObjectByLabel(objectName):
  for oo in FreeCAD.ActiveDocument.Objects:
    if oo.Label == objectName:
      return oo
  return None

def getPointOnCylinder(ax, ay, Radius):
  angle = ax/Radius
  result = (math.cos(angle)*Radius, math.sin(angle)*Radius, ay)
  return result

def WrapBSpline(bspline, Radius, XScale):
  poles = bspline.getPoles()
  newpoles=[]
  for pp in poles:
    newpoles.append(getPointOnCylinder(pp[0]*XScale, pp[1], Radius))

  newbspline = bspline
  newbspline.buildFromPolesMultsKnots(newpoles, bspline.getMultiplicities(), bspline.getKnots(), bspline.isPeriodic(), bspline.Degree, bspline.getWeights())
  return newbspline.toShape()

def WrapSketch(Sketch, Radius, XScale):
  Edges = []
  for gg in Sketch.Geometry:
    if (gg.Construction):
      continue
    if str(type(gg)) == "<type 'Part.GeomArcOfCircle'>":
      bspline = gg.toBSpline()
      Edges.append(WrapBSpline(bspline, Radius, XScale))
    elif str(type(gg)) == "<type 'Part.GeomLineSegment'>":
      sp = gg.StartPoint
      ep = gg.EndPoint
      if (sp.x > ep.x):
        tt = sp
        sp = ep
        ep = tt

      sp.x = sp.x*XScale
      ep.x = ep.x*XScale
      if (sp.x == ep.x):
        Edges.append(Part.makeLine(getPointOnCylinder(sp.x, sp.y, Radius), getPointOnCylinder(ep.x, ep.y, Radius)))
      elif (sp.y == ep.y):
        Angle0 = sp.x/Radius*180/math.pi
        Angle1 = ep.x/Radius*180/math.pi
        cc = Part.makeCircle(Radius, Base.Vector(0, 0, gg.StartPoint.y), Base.Vector(0, 0, 1), Angle0, Angle1)
        Edges.append(cc)
      else:
        hh = Part.makeHelix(2*abs(ep.y - sp.y)*math.pi/(abs(ep.x - sp.x)/Radius), abs(ep.y - sp.y), Radius, 0, (sp.y > ep.y))
        if (sp.y > ep.y):
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), ep.x/Radius*180/math.pi);
        else:
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), sp.x/Radius*180/math.pi);
        hh.Placement.Base = Base.Vector(0, 0, min(sp.y, ep.y))
        Edges.append(hh)
  return Edges


class WrappedSketch:
  def __init__(self, obj, SketchLabel = "", RuledRadius = 20, Thickness = 2):

    PropertyGroup = "WrappedSketch";
    obj.addProperty("App::PropertyString", "SketchLabel", PropertyGroup, "").SketchLabel = SketchLabel

    obj.addProperty("App::PropertyFloat", "RuledRadius", PropertyGroup, "Radius for outer surface").RuledRadius = RuledRadius
    obj.addProperty("App::PropertyFloat", "Thickness", PropertyGroup, "Thickness of extrusion").Thickness = Thickness
    obj.Proxy = self

  def execute(self, obj):
    Sketch = GetObjectByLabel(obj.SketchLabel)
    if (Sketch == None):
      return

    InnerRadius = obj.RuledRadius - obj.Thickness

    Edges = WrapSketch(Sketch, obj.RuledRadius, 1.0)
    Shape = Part.makeCompound(Edges)
    Face1 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    Edges = WrapSketch(Sketch, InnerRadius, InnerRadius/obj.RuledRadius)
    Shape = Part.makeCompound(Edges)
    Face2 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    loft = Part.makeLoft([Face1.OuterWire, Face2.OuterWire], True, True);
    obj.Shape = loft.removeSplitter()


def makeWrappedSketch():
  if FreeCAD.ActiveDocument is None:
    return

  aSketchLabel = ""
  selections=FreeCAD.Gui.Selection.getSelectionEx()
  if len(selections) > 0:
    selObject = selections[0]
    if str(type(selObject.Object)) == "<type 'Sketcher.SketchObject'>":
      aSketchLabel = selObject.Object.Label

  a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","WrappedSketch")
  WrappedSketch(a, SketchLabel=aSketchLabel)
  a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
  FreeCAD.ActiveDocument.recompute()
  FreeCADGui.ActiveDocument.ActiveView.fitAll()
  return a
FC04.JPG
FC04.JPG (20.85 KiB) Viewed 13184 times
FC05.JPG
FC05.JPG (33.85 KiB) Viewed 13184 times
FC06.JPG
FC06.JPG (42.87 KiB) Viewed 13184 times
flokart
Posts: 21
Joined: Sun Apr 10, 2016 12:15 pm

Re: Wrapping a sketch around a cylinder

Post by flokart »

could someone explain the workflow to use this macro?
I selected the cylinder and than the sketch, run the macro but nothing happens..

Thanks in advanced
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

I Made it work but adding the following line at end of the script:
makeWrappedSketch()

But The script only use 1 sketch as input... the radius of the cylinder must be set after transformation in object Property...

BTW Thanks for this great piece of code!!! really usefull!!

Regards,

Stonedge
flokart
Posts: 21
Joined: Sun Apr 10, 2016 12:15 pm

Re: Wrapping a sketch around a cylinder

Post by flokart »

stonedge thanks man..works great....and also a big thanks to the coder of the script.....extreme useful.
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

I've done some progress over that one! since I was looking to wrap a multi object dxf, I've modify the macro to handle multi draft selection(honestly this is only a small fix ... all praise and credit go to Allcome the original author ! thx again for you work!). And It work great :)

so here the resulting macro (also add as attachment the icon that I use on toolbar)

PolarExtrude.FCMacro

Code: Select all

from __future__ import division # allows floating point division from integers
import FreeCAD, FreeCADGui
from FreeCAD import Base
import Part
import math

def GetObjectByLabel(objectName):
  for oo in FreeCAD.ActiveDocument.Objects:
    if oo.Label == objectName:
      return oo
  return None

def getPointOnCylinder(ax, ay, Radius):
  angle = ax/Radius
  result = (math.cos(angle)*Radius, math.sin(angle)*Radius, ay)
  return result

def WrapBSpline(bspline, Radius, XScale):
  poles = bspline.getPoles()
  newpoles=[]
  for pp in poles:
    newpoles.append(getPointOnCylinder(pp[0]*XScale, pp[1], Radius))

  newbspline = bspline
  newbspline.buildFromPolesMultsKnots(newpoles, bspline.getMultiplicities(), bspline.getKnots(), bspline.isPeriodic(), bspline.Degree, bspline.getWeights())
  return newbspline.toShape()

def WrapSketch(Sketch, Radius, XScale):
  Edges = []
  for gg in Sketch.Geometry:
    if (gg.Construction):
      continue
    if str(type(gg)) == "<type 'Part.GeomArcOfCircle'>":
      bspline = gg.toBSpline()
      Edges.append(WrapBSpline(bspline, Radius, XScale))
    elif str(type(gg)) == "<type 'Part.GeomLineSegment'>":
      sp = gg.StartPoint
      ep = gg.EndPoint
      if (sp.x > ep.x):
        tt = sp
        sp = ep
        ep = tt

      sp.x = sp.x*XScale
      ep.x = ep.x*XScale
      if (sp.x == ep.x):
        Edges.append(Part.makeLine(getPointOnCylinder(sp.x, sp.y, Radius), getPointOnCylinder(ep.x, ep.y, Radius)))
      elif (sp.y == ep.y):
        Angle0 = sp.x/Radius*180/math.pi
        Angle1 = ep.x/Radius*180/math.pi
        cc = Part.makeCircle(Radius, Base.Vector(0, 0, gg.StartPoint.y), Base.Vector(0, 0, 1), Angle0, Angle1)
        Edges.append(cc)
      else:
        hh = Part.makeHelix(2*abs(ep.y - sp.y)*math.pi/(abs(ep.x - sp.x)/Radius), abs(ep.y - sp.y), Radius, 0, (sp.y > ep.y))
        if (sp.y > ep.y):
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), ep.x/Radius*180/math.pi);
        else:
          hh.rotate(Base.Vector(0,0,0), Base.Vector(0,0,1), sp.x/Radius*180/math.pi);
        hh.Placement.Base = Base.Vector(0, 0, min(sp.y, ep.y))
        Edges.append(hh)
  return Edges


class WrappedSketch:
  def __init__(self, obj, SketchLabel = "", RuledRadius = 50, Thickness = 2):

    PropertyGroup = "WrappedSketch";
    obj.addProperty("App::PropertyString", "SketchLabel", PropertyGroup, "").SketchLabel = SketchLabel

    obj.addProperty("App::PropertyFloat", "RuledRadius", PropertyGroup, "Radius for outer surface").RuledRadius = RuledRadius
    obj.addProperty("App::PropertyFloat", "Thickness", PropertyGroup, "Thickness of extrusion").Thickness = Thickness
    obj.Proxy = self

  def execute(self, obj):
    Sketch = GetObjectByLabel(obj.SketchLabel)
    if (Sketch == None):
      return

    InnerRadius = obj.RuledRadius - obj.Thickness

    Edges = WrapSketch(Sketch, obj.RuledRadius, 1.0)
    Shape = Part.makeCompound(Edges)
    Face1 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    Edges = WrapSketch(Sketch, InnerRadius, InnerRadius/obj.RuledRadius)
    Shape = Part.makeCompound(Edges)
    Face2 = Part.makeFilledFace(Part.__sortEdges__(Shape.Edges))

    loft = Part.makeLoft([Face1.OuterWire, Face2.OuterWire], True, True);
    obj.Shape = loft.removeSplitter()


def makeWrappedSketch():
  if FreeCAD.ActiveDocument is None:
    return
  
  aSketchLabel = ""
  selections=FreeCAD.Gui.Selection.getSelectionEx()
  if len(selections) > 0:
    for selObject in selections:
		aSketchLabel = ""
		if str(type(selObject.Object)) == "<type 'Sketcher.SketchObject'>":
			aSketchLabel = selObject.Object.Label
		FreeCAD.Console.PrintMessage("Processing:"+ aSketchLabel +"\n")
		a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","WrappedSketch")
		WrappedSketch(a, SketchLabel=aSketchLabel)
		a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
  FreeCAD.ActiveDocument.recompute()
  FreeCADGui.ActiveDocument.ActiveView.fitAll()
  return a

FreeCAD.Console.PrintMessage("makeWrappedSketch Start"+"\n")
makeWrappedSketch()
FreeCAD.Console.PrintMessage("makeWrappedSketch End"+"\n")


And here an example of what you could acheive :)
WedRool-Center.png
WedRool-Center.png (52.56 KiB) Viewed 12804 times
WedRool.png
WedRool.png (53.27 KiB) Viewed 12804 times
Attachments
PolarExtrude.png
PolarExtrude.png (32.9 KiB) Viewed 12804 times
User avatar
quick61
Veteran
Posts: 3803
Joined: Sat Aug 24, 2013 2:49 am
Location: u.S.A.

Re: Wrapping a sketch around a cylinder

Post by quick61 »

flokart wrote:could someone explain the workflow to use this macro?
I selected the cylinder and than the sketch, run the macro but nothing happens..

Thanks in advanced
+1

I am not getting the workflow for this either. being that I totally suck rocks at Python and these macros, I too would like to know just how to use the script. Will this latest version by Stonedge work with Draft Wires, and if so, how?

Mark
This post made with 0.0% Micro$oft products - GOT LINUX?
Post Reply