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!
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

Hi Quick61,

Here how to install it in your freecad (i've test it over 0.16 and 0.17RC successfully):

Copy the code and save it as a .FCMacro files (eg. PolarExtrude.FCMacro) in your user freecad macro dir (windows: c:\users\"yourusername"\AppData\Roaming\FreeCAD\Macro linux: ~/.freecad/macro (I think))

In freecad select one or more sketch (Highlight it in the tree) and click on the macro menu at top --> macro ... --> Select PolarExtrude.FCMacro and click execute.

Voila! you've done.

After you could modify the radius and extrusion thickness on the generated object properties.

If you wish you could also add an icon to a toolbar for easier access...
flokart
Posts: 21
Joined: Sun Apr 10, 2016 12:15 pm

Re: Wrapping a sketch around a cylinder

Post by flokart »

Stonedge do you think this code could be easy modified to use it the other way around ?
Would be awsome to unwrappe a cirlce/curved face.I know there exist already some macros like.
-Macro Unroll Ruled Surface (seams to work only for predefined part objects like zylinders....but all other tested surfaces dont work)
-Macro Unfold Box (only for box)
-Macro Sheet Metal Unfolder (seams only for sheet metal objects..but i think some parts of the code are for surfaces to...maybe)

But nothing seams to work only for a curved surface.

I tested your code ....very cool...is it true that only multy sketch selection is possible and not draft object....so to use it you have to convert draft objects to sketch objects.
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

Could you give me an example of what you are trying to achieve? kind of what pepakura do? flatten a curve surface for 2d cutting?

That kind of flattening would normally generate cut over the face, it is what you looking for?

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

Re: Wrapping a sketch around a cylinder

Post by flokart »

Here is an example what i try to achieve. I would say pepakura does the same but its for Mesh models (flat faces) and not for Curve Surfaces.
Unwrapping meshes with defined seams its not a problem in blender. The result is achieved with unroll in Rhino.
Image
Image
Image

Hope it gives you an idea what i am looking for.
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

Ok After playing a little(... a lot...) with that macro it seem that it have some issue with circle.... most of the time playing lightly with the sketch fix the issue... but I've finnally hit the limit of that trick with one of my design...

So I've use that occasion to fire up my python debugger and start debugging... finally found it Float Precision issue with a direct equallity comparaison!

Here the Change:
if (sp.x == ep.x): --> if (round(sp.x,3) == round(ep.x,3)):
and
elif (sp.y == ep.y: --> elif (round(sp.y,3) == round(ep.y,3)):

and Here the completed fixed macro:
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 (round(sp.x,3) == round(ep.x,3)):
        Edges.append(Part.makeLine(getPointOnCylinder(sp.x, sp.y, Radius), getPointOnCylinder(ep.x, ep.y, Radius)))
      elif (round(sp.y,3) == round(ep.y,3)):
        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")
Now this Macro Work very well with complex sketch...

but don't forget that it remain some limitation:

since it use loft, the sketch must be a close face ( no hole... see loft documentation)
Currently unable to made a design do more than 180 deg... you must do it using 2 or more sketch

Again, all the credit go to original author!

Enjoy

Stonedge
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 »

Stonedge wrote:round(sp.x,3) == round(ep.x,3)
This is still an incorrect way of comparing floats. Because round(0.0005000001, 3) is not equal to round(0.0004999999, 3)
Use, for example, abs(sp.x - ep.x) < tolerance
Stonedge
Posts: 9
Joined: Fri Feb 14, 2014 2:43 pm
Location: Quebec, Canada

Re: Wrapping a sketch around a cylinder

Post by Stonedge »

Thanks DeepSOIC for the hint!

I know it was a quick dirty fix/hack!

So here is the Correctly Fixed Version...BTW I've set the tolerance to 0.001 (test)

Stonedge

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 (abs(sp.x - ep.x) < 0.001):
        Edges.append(Part.makeLine(getPointOnCylinder(sp.x, sp.y, Radius), getPointOnCylinder(ep.x, ep.y, Radius)))
      elif (abs(sp.y - ep.y) < 0.001):
        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")
orc101
Posts: 12
Joined: Fri Jul 15, 2016 7:03 pm

Re: Wrapping a sketch around a cylinder

Post by orc101 »

OS: Windows 7
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.16.6704 (Git)
Build type: Release
Branch: releases/FreeCAD-0-16
Hash: 0c449d7e8f9b2b1fb93e3f8d1865e2f59d7ed253
Python version: 2.7.8
Qt version: 4.8.7
Coin version: 4.0.0a
OCC version: 6.8.0.oce-0.17

Hi Stonedge,

I have been trying to wrap an egg shape sketch around a cylinder using the macro you shared so kindly. I am getting some rather strange results. Have you any idea where I may be going wrong please? Any help greatly appreciated.

Oliver
Curved_egg_shape.PNG
Curved_egg_shape.PNG (81.1 KiB) Viewed 4837 times
Attachments
Curved_egg_shape_0.1.FCStd
(57.55 KiB) Downloaded 91 times
chrisb
Veteran
Posts: 53930
Joined: Tue Mar 17, 2015 9:14 am

Re: Wrapping a sketch around a cylinder

Post by chrisb »

You may look in the german forum: viewtopic.php?f=13&t=17983. microcelly2 has put considerable work in further development of the macro.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
orc101
Posts: 12
Joined: Fri Jul 15, 2016 7:03 pm

Re: Wrapping a sketch around a cylinder

Post by orc101 »

Thanks Chris,

I tried with the macro from the German forum but the work flow was not clear. Pretty sure it is a small detail I am missing. I will wait to see if Stoneedge can save me from myself :)

Oliver
Post Reply