Skip to content

Instantly share code, notes, and snippets.

@pgolay
Last active July 21, 2018 03:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pgolay/4b0bb678c036346a8efa2596cb8abcdc to your computer and use it in GitHub Desktop.
Save pgolay/4b0bb678c036346a8efa2596cb8abcdc to your computer and use it in GitHub Desktop.
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import System.Guid
import math
def GetMarkerOptions(crvLength):
tol = sc.doc.ModelAbsoluteTolerance
lFonts = [item.lower() for item in sorted(list(set(Rhino.DocObjects.Font.AvailableFontFaceNames())))]
fonts = [item.title() for item in lFonts] #to use in a list box if needed
for item in fonts:
if Rhino.DocObjects.Font(item).IsSingleStrokeFont:
fonts.insert(0, fonts.pop(fonts.index(item)))
while True:
num = 0
if sc.sticky.has_key("SEW_START_NUMBER"):
num = sc.sticky["SEW_START_NUMBER"]
prefix = None
if sc.sticky.has_key("SEW_NUMBER_PREFIX"):
prefix = sc.sticky["SEW_NUMBER_PREFIX"]
addNum = True
if sc.sticky.has_key("ADD_SEW_NUMBERS"):
addNum = sc.sticky["ADD_SEW_NUMBERS"]
dblSize = 1
if sc.sticky.has_key("SEAM_MARKER_SIZE"):
dblSize = sc.sticky["SEAM_MARKER_SIZE"]
divLen = 1
if sc.sticky.has_key("SEW_DIV_LENGTH"):
divLen = sc.sticky["SEW_DIV_LENGTH"]
divNum = 5
if sc.sticky.has_key("SEW_DIV_NUM"):
divNum = sc.sticky["SEW_DIV_NUM"]
mode = False
if sc.sticky.has_key("SEW_DIV_MODE"):
mode = sc.sticky["SEW_DIV_MODE"]
extension = 0
if sc.sticky.has_key("SEW_HASH_EXTENSION"):
extension = sc.sticky["SEW_HASH_EXTENSION"]
fontName= "Arial"
if sc.sticky.has_key("SEW_FONTNAME"):
fontName = sc.sticky ["SEW_FONTNAME"].title()
hole = False
if sc.sticky.has_key("SEW_HOLE_STATUS"):
hole = sc.sticky ["SEW_HOLE_STATUS"]
go = Rhino.Input.Custom.GetOption()
go.AcceptNothing(True)
strGo = "Curve segment length is " + str(crvLength) + " Press Enter to accept the current settings."
go.SetCommandPrompt(strGo)
go.AcceptNumber(True, True)
opSize = Rhino.Input.Custom.OptionDouble(dblSize)
opNum = Rhino.Input.Custom.OptionInteger(num)
opDivLen = Rhino.Input.Custom.OptionDouble(divLen)
opDivNum = Rhino.Input.Custom.OptionInteger(divNum)
opMode = Rhino.Input.Custom.OptionToggle(mode, "Number", "Length")
opAddNum = Rhino.Input.Custom.OptionToggle(addNum, "No", "Yes")
opHashExtension = Rhino.Input.Custom.OptionDouble(extension, True, tol*10)
opFont = go.AddOption("Font", fontName)
opHole = Rhino.Input.Custom.OptionToggle(hole, "No", "Yes")
go.AddOptionToggle("Mode",opMode)
go.AddOptionInteger("CurrentNumber", opNum)
go.AddOptionDouble("MarkerSize", opSize)
#Decide what division option to add
if mode:
go.AddOptionDouble("DivisionLength", opDivLen)
else:
go.AddOptionInteger("DivisionNumber", opDivNum)
go.AddOptionDouble("HashMarkExtension", opHashExtension)
go.AddOptionToggle("Numbers", opAddNum)
go.AddOptionToggle("Hole", opHole)
result = go.Get()
if( go.CommandResult() != Rhino.Commands.Result.Success ):
return False, mode,dblSize,num,divLen,divNum, addNum, fontName, extension, hole
if result == Rhino.Input.GetResult.Number:
divNum = int(go.Number())
sc.sticky["SEW_DIV_NUM"] = divNum
continue
if result == Rhino.Input.GetResult.Nothing:
sc.sticky["SEW_START_NUMBER"]= num
return True, mode,dblSize,num,divLen,divNum, addNum, fontName, extension, hole
if result == Rhino.Input.GetResult.Option:
opIdx = go.OptionIndex()
if opIdx == opFont:
#fontRC, tempFont = Rhino.Input.RhinoGet.GetString("Font name", True, fontName)
gf = Rhino.Input.Custom.GetString()
gf.SetCommandPrompt("Set new marker font.")
gf.AddOption("List")
fontRC = gf.Get()
if fontRC == Rhino.Input.GetResult.Option:
tempFont = rs.ListBox(fonts, "Set marker font", fonts[fonts.index(fontName)])
else:
tempFont == gf.String()
if tempFont.lower() in lFonts:
fontName = tempFont
#sc.doc.Fonts.FindOrCreate(fontName,False, False)
sc.sticky["SEW_FONTNAME"]= fontName.title()
mode = opMode.CurrentValue
dblSize = opSize.CurrentValue
num = opNum.CurrentValue
divLen = opDivLen.CurrentValue
divNum = opDivNum.CurrentValue
addNum = opAddNum.CurrentValue
extension = opHashExtension.CurrentValue
hole = opHole.CurrentValue
sc.sticky["ADD_SEW_NUMBERS"] = addNum
sc.sticky["SEW_NUMBER"] = num
sc.sticky["SEW_START_NUMBER"]= num
sc.sticky["SEAM_MARKER_SIZE"] = dblSize
sc.sticky["SEW_DIV_LENGTH"] = divLen
sc.sticky["SEW_DIV_NUM"] = divNum
sc.sticky["SEW_DIV_MODE"] = mode
sc.sticky["SEW_HASH_EXTENSION"] = extension
sc.sticky["SEW_HOLE_STATUS"] = hole
continue
def EdgeDivider():
pi = math.pi
style = Rhino.Geometry.CurveOffsetCornerStyle.None
count = 0
startNum = 0
if sc.sticky.has_key("SEW_START_NUMBER"):
startNum = sc.sticky["SEW_START_NUMBER"]
def PlanarClosedCurveFilter(rhObject, geometry, componentIndex):
pcc = False
if geometry.IsPlanar and geometry.IsClosed:
pcc = True
return pcc
while count < 2:
count +=1
grp = rs.AddGroup()
edgeStr = "Select a curve segment to mark near starting end."
if count > 1:
edgeStr = "Select the second curve segment near the starting end."
#Get the curve to divide
ge = Rhino.Input.Custom.GetObject()
ge.GeometryFilter = Rhino.DocObjects.ObjectType.Curve
ge.SetCustomGeometryFilter(PlanarClosedCurveFilter)
ge.EnableHighlight(False)
ge.DisablePreSelect()
ge.SetCommandPrompt(edgeStr)
rc = ge.Get()
if( ge.CommandResult() != Rhino.Commands.Result.Success ):
return
if rc == Rhino.Input.GetResult.Object:
objref = ge.Object(0)
tempCrv = objref.Curve()
full_crv, curve_parameter = objref.CurveParameter()
tempPt = full_crv.PointAt(curve_parameter)
if not isinstance(tempCrv, Rhino.Geometry.PolyCurve):
if isinstance(tempCrv, Rhino.Geometry.PolylineCurve):
full_crv = Rhino.Geometry.PolyCurve()
for i in range(tempCrv.PointCount-1):
full_crv.Append(Rhino.Geometry.LineCurve(tempCrv.Point(i),tempCrv.Point(i+1)))
if not full_crv.IsClosed:
print "Could not make a closed curve"
return
curve_parameter = full_crv.ClosestPoint(tempPt)[1]
else:
simStyle = Rhino.Geometry.CurveSimplifyOptions.RebuildLines|Rhino.Geometry.CurveSimplifyOptions.RebuildArcs
tempCrv = tempCrv.Simplify(simStyle, .001, Rhino.RhinoMath.ToRadians(1))
if not isinstance(full_crv, Rhino.Geometry.NurbsCurve):
seg_par = full_crv.SegmentCurveParameter(curve_parameter)
idx = full_crv.SegmentIndex(curve_parameter)
e = full_crv.SegmentCurve(idx)
eCrv = e.ToNurbsCurve()
else:
eCrv = full_crv
#Fake flashing on selection
xx = sc.doc.Objects.AddCurve(eCrv)
tObj = sc.doc.Objects.Find(xx)
sc.doc.Views.FlashObjects([tObj], True)
sc.doc.Objects.Delete(tObj)
pPt = objref.SelectionPoint()
if count == 1: crvLength = round(eCrv.GetLength(), 3)
#Get the options for dividing and marking the edges
if count == 1:
RC, mode,dblSize,num,divLen,divNum,addNum, fontName, extension, hole = GetMarkerOptions(crvLength)
if not RC: return
if count > 1:
if sc.sticky.has_key("SEW_START_NUMBER"):
num = sc.sticky["SEW_START_NUMBER"]
if not eCrv: return
#start counting from the picked end of the curve
eDom = eCrv.Domain
x = eCrv.ClosestPoint(pPt)
if eCrv.ClosestPoint(pPt)[1]>eDom.Mid:
eCrv.Reverse()
#print "Reversed"
#Set the right kind of curve division
if mode:#Use length of division
pars = eCrv.DivideByLength(divLen, True)
else: #Use number of divisions
pars = eCrv.DivideByCount(divNum, True)
WXY = Rhino.Geometry.Plane.WorldXY
WXY.Origin = eCrv.PointAtStart
planeRC,crvPlane = full_crv.TryGetPlane()
if planeRC:
planeAngle = Rhino.Geometry.Vector3d.VectorAngle(WXY.ZAxis, crvPlane.ZAxis)
if planeAngle > pi/2: crvPlane.Flip()
else:
crvPlane = WXY
vecPerp = eCrv.TangentAt(pars[1])
vecPerp.Rotate(pi/2,crvPlane.ZAxis)
pt = eCrv.PointAt(pars[1])
if full_crv.Contains(pt+vecPerp) == Rhino.Geometry.PointContainment.Inside:
if hole: vecPerp = vecPerp*-1
offsetCrv = eCrv.Offset(pt + -1*vecPerp, crvPlane.ZAxis ,dblSize,.001, style)[0]
else:
if hole: vecPerp = vecPerp*-1
offsetCrv = eCrv.Offset(pt + vecPerp, crvPlane.ZAxis ,dblSize,.001, style)[0]
sc.doc.Objects.AddCurve(offsetCrv)
for i in range(len(pars)):
offset=True
pt = eCrv.PointAt(pars[i])
#rs.AddPoint(pt)
rc, plane = eCrv.PerpendicularFrameAt(pars[i])
circle = Rhino.Geometry.Circle(plane, dblSize)
#Intersect a circle with the World XY plane to get two points marking the ends of the hash mark
ppp = Rhino.Geometry.Intersect.Intersection.CurvePlane(circle.ToNurbsCurve(), crvPlane, .001)
if ppp is None:
print "The curve plane may not be parallel to the World XY plane."
return
p1 = ppp[0].PointA
p2 = ppp[1].PointA
if offsetCrv.PointAt(offsetCrv.ClosestPoint(pt)[1]).DistanceTo(p1) > offsetCrv.PointAt(offsetCrv.ClosestPoint(pt)[1]).DistanceTo(p2):
vecY = pt-p1
else:
vecY = pt-p2
hashMark = Rhino.Geometry.LineCurve(pt, pt+vecY)
vecY.Unitize
if extension > 0:
hashMark = hashMark.Extend(Rhino.Geometry.CurveEnd.Start, extension, Rhino.Geometry.CurveExtensionStyle.Line)
#if numbering is asked for by the user, get the correct plane etc, and add number-shaped curves.
if addNum: #Numbering enabled
directionsMatch=False
vecX= Rhino.Geometry.Vector3d(vecY)
vecX.Rotate( -pi/2,crvPlane.ZAxis)
tPlane = Rhino.Geometry.Plane(pt,vecX,vecY)
if eCrv.FrameAt(pars[i])[1].XAxis.IsParallelTo( tPlane.XAxis) == 1:
directionsMatch=True
blnClose = True
if Rhino.DocObjects.Font(fontName).IsSingleStrokeFont: blnClose = False
text = Rhino.Geometry.Curve.CreateTextOutlines(str(num),fontName, .5*dblSize, 0, blnClose, tPlane,.8*dblSize, tolerance=.001)
#TEXT = Rhino.Geometry.TextEntity()
#TEXT.Plane = tPlane
#TEXT.Text = str(num)
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
#TEXT.FontIndex = sc.doc.Fonts.FindOrCreate("Arial",False, False)
#sc.doc.Objects.AddText(TEXT)
if text:
textBB = Rhino.Geometry.BoundingBox()
for txtCrv in text:
textBB.Union(txtCrv.GetBoundingBox(tPlane))
pass
corners = textBB.GetCorners()
pToP = Rhino.Geometry.Transform.PlaneToPlane(Rhino.Geometry.Plane.WorldXY,tPlane)
for u in range(8):
corners[u] = pToP*corners[u]
tempPt = corners[1]
if directionsMatch:
if count > 1:
if i > 0:
xXform = (pt-corners[1])*1.1
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomRight
else:
xXform = None
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
else:
if i > 0:
xXform = (pt-corners[1])*1.1
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomRight
else:
xXform = None
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
else:
if count > 1:
if i < len(pars)-1:
xXform = (pt-corners[1])*1.1
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomRight
else:
xXform = None
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
else:
if 0< i < len(pars)-1:
xXform = None
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
elif i == len(pars)-1 :
xXform = None
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomLeft
elif i == 0:
xXform = (pt-corners[1]).Length*eCrv.TangentAtStart*1.1
#TEXT.Justification= Rhino.Geometry.TextJustification.BottomRight
#xXform.Reverse()
#Adjust the Y location
yXform = tPlane.YAxis*.2*dblSize
#yXform.Reverse()
tempIds = []
#tPlane.Rotate( pi, tPlane.ZAxis)
#TEXT.Plane = tPlane
#TEXT.Justification= Rhino.Geometry.TextJustification.TopRight
#sc.doc.Objects.AddText(TEXT)
for txtCrv in text:
if xXform is not None: txtCrv.Translate(xXform)
txtCrv.Translate(yXform)
tempIds.append(sc.doc.Objects.AddCurve(txtCrv))
rs.AddObjectsToGroup(tempIds, rs.AddGroup())
rs.AddObjectsToGroup(tempIds, grp)
addId = System.Guid.NewGuid()
crvId = sc.doc.Objects.AddCurve(hashMark)
rs.AddObjectToGroup(crvId, grp)
rs.SetUserText(crvId, "SeamMarkerId", str(addId))
addId = None
if addNum: num += 1
sc.sticky["SEW_NUMBER"] = num
sc.doc.Objects.UnselectAll()
sc.doc.Views.Redraw()
sc.sticky["SEW_START_NUMBER"] = num
if __name__ == "__main__": EdgeDivider()
@pgolay
Copy link
Author

pgolay commented Jul 20, 2018

First crack at adding the ability to set a font and accommodate single stroke fonts.

@pgolay
Copy link
Author

pgolay commented Jul 20, 2018

Allow extension of the hash marks.

@pgolay
Copy link
Author

pgolay commented Jul 21, 2018

Added 'Hole=Yes/No' toggle option to offset to the inside of the curve if set to Yes.

@pgolay
Copy link
Author

pgolay commented Jul 21, 2018

Filter single stroke fonts to the top of the list in the Font option > List

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment