osut 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
osut/osut.py CHANGED
@@ -107,8 +107,8 @@ _film = dict(
107
107
 
108
108
  # Default (~1980s) envelope Uo (W/m2•K), based on surface type.
109
109
  _uo = dict(
110
- shading = None, # N/A
111
- partition = None, # N/A
110
+ shading = None, # N/A
111
+ partition = None, # N/A
112
112
  wall = 0.384, # rated R14.8 hr•ft2F/Btu
113
113
  roof = 0.327, # rated R17.6 hr•ft2F/Btu
114
114
  floor = 0.317, # rated R17.9 hr•ft2F/Btu (exposed floor)
@@ -335,9 +335,8 @@ def genConstruction(model=None, specs=dict()):
335
335
  ide = "OSut.CON." + specs["type"]
336
336
  if specs["type"] not in uo():
337
337
  return oslg.invalid("surface type", mth, 2, CN.ERR)
338
- if "uo" not in specs:
339
- specs["uo"] = uo()[ specs["type"] ]
340
338
 
339
+ if "uo" not in specs: specs["uo"] = uo()[ specs["type"] ] # can be None
341
340
  u = specs["uo"]
342
341
 
343
342
  if u:
@@ -348,6 +347,8 @@ def genConstruction(model=None, specs=dict()):
348
347
 
349
348
  if u < 0:
350
349
  return oslg.negative(id + " Uo", mth, CN.ERR)
350
+ if round(u, 2) == 0:
351
+ return oslg.zero(id + " Uo", mth, CN.ERR)
351
352
  if u > 5.678:
352
353
  return oslg.invalid(id + " Uo (> 5.678)", mth, 2, CN.ERR)
353
354
 
@@ -581,7 +582,7 @@ def genConstruction(model=None, specs=dict()):
581
582
  a["compo" ]["id" ] = "OSut." + mt + ".%03d" % int(d * 1000)
582
583
 
583
584
  elif specs["type"] == "window":
584
- a["glazing"]["u" ] = specs["uo"]
585
+ a["glazing"]["u" ] = u if u else uo()["window"]
585
586
  a["glazing"]["shgc"] = 0.450
586
587
  if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"]
587
588
  a["glazing"]["id" ] = "OSut.window"
@@ -589,7 +590,7 @@ def genConstruction(model=None, specs=dict()):
589
590
  a["glazing"]["id" ] += ".SHGC%d" % (a["glazing"]["shgc"]*100)
590
591
 
591
592
  elif specs["type"] == "skylight":
592
- a["glazing"]["u" ] = specs["uo"]
593
+ a["glazing"]["u" ] = u if u else uo()["skylight"]
593
594
  a["glazing"]["shgc"] = 0.450
594
595
  if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"]
595
596
  a["glazing"]["id" ] = "OSut.skylight"
@@ -599,14 +600,14 @@ def genConstruction(model=None, specs=dict()):
599
600
  if a["glazing"]:
600
601
  layers = openstudio.model.FenestrationMaterialVector()
601
602
 
602
- u = a["glazing"]["u" ]
603
+ u0 = a["glazing"]["u" ]
603
604
  shgc = a["glazing"]["shgc"]
604
605
  lyr = model.getSimpleGlazingByName(a["glazing"]["id"])
605
606
 
606
607
  if lyr:
607
608
  lyr = lyr.get()
608
609
  else:
609
- lyr = openstudio.model.SimpleGlazing(model, u, shgc)
610
+ lyr = openstudio.model.SimpleGlazing(model, u0, shgc)
610
611
  lyr.setName(a["glazing"]["id"])
611
612
 
612
613
  layers.append(lyr)
@@ -635,49 +636,54 @@ def genConstruction(model=None, specs=dict()):
635
636
 
636
637
  layers.append(lyr)
637
638
 
638
- c = openstudio.model.Construction(layers)
639
+ c = openstudio.model.Construction(layers)
639
640
  c.setName(ide)
640
641
 
641
642
  # Adjust insulating layer thickness or conductivity to match requested Uo.
642
- if not a["glazing"]:
643
- ro = 1 / specs["uo"] - film()[specs["type"]] if specs["uo"] else 0
643
+ if u and not a["glazing"]:
644
+ ro = 1 / u - flm
644
645
 
645
- if specs["type"] == "door": # 1x layer, adjust conductivity
646
- layer = c.getLayer(0).to_StandardOpaqueMaterial()
646
+ if ro > 0:
647
+ if specs["type"] == "door": # 1x layer, adjust conductivity
648
+ layer = c.getLayer(0).to_StandardOpaqueMaterial()
647
649
 
648
- if not layer:
649
- return oslg.invalid(id + " standard material?", mth, 0)
650
+ if not layer:
651
+ return oslg.invalid(id + " standard material?", mth, 0)
650
652
 
651
- layer = layer.get()
652
- k = layer.thickness() / ro
653
- layer.setConductivity(k)
653
+ layer = layer.get()
654
+ k = layer.thickness() / ro
655
+ layer.setConductivity(k)
654
656
 
655
- elif ro > 0: # multiple layers, adjust insulating layer thickness
656
- lyr = insulatingLayer(c)
657
+ else: # multiple layers, adjust insulating layer thickness
658
+ lyr = insulatingLayer(c)
657
659
 
658
- if not lyr["index"] or not lyr["type"] or not lyr["r"]:
659
- return oslg.invalid(id + " construction", mth, 0)
660
+ if not lyr["index"] or not lyr["type"] or not lyr["r"]:
661
+ return oslg.invalid(id + " construction", mth, 0)
660
662
 
661
- index = lyr["index"]
662
- layer = c.getLayer(index).to_StandardOpaqueMaterial()
663
+ index = lyr["index"]
664
+ layer = c.getLayer(index).to_StandardOpaqueMaterial()
663
665
 
664
- if not layer:
665
- return oslg.invalid(id + " material %d" % index, mth, 0)
666
+ if not layer:
667
+ return oslg.invalid(id + " material %d" % index, mth, 0)
666
668
 
667
- layer = layer.get()
668
- k = layer.conductivity()
669
- d = (ro - rsi(c) + lyr["r"]) * k
669
+ layer = layer.get()
670
+ k = layer.conductivity()
671
+ d = (ro - rsi(c) + lyr["r"]) * k
670
672
 
671
- if d < 0.03:
672
- return oslg.invalid(id + " adjusted material thickness", mth, 0)
673
+ if d < 0.03:
674
+ m = id + " adjusted material thickness"
675
+ return oslg.invalid(m, mth, 0)
673
676
 
674
- nom = re.sub(r'[^a-zA-Z]', '', layer.nameString())
675
- nom = re.sub(r'OSut', '', nom)
676
- nom = "OSut." + nom + ".%03d" % int(d * 1000)
677
+ nom = re.sub(r'[^a-zA-Z]', '', layer.nameString())
678
+ nom = re.sub(r'OSut', '', nom)
679
+ nom = "OSut." + nom + ".%03d" % int(d * 1000)
677
680
 
678
- if not model.getStandardOpaqueMaterialByName(nom):
679
- layer.setName(nom)
680
- layer.setThickness(d)
681
+ if model.getStandardOpaqueMaterialByName(nom):
682
+ omat = model.getStandardOpaqueMaterialByName(nom).get()
683
+ c.setLayer(index, omat)
684
+ else:
685
+ layer.setName(nom)
686
+ layer.setThickness(d)
681
687
 
682
688
  return c
683
689
 
@@ -1650,7 +1656,7 @@ def scheduleIntervalMinMax(sched=None) -> dict:
1650
1656
  - "min" (float): min temperature. (None if invalid inputs - see logs).
1651
1657
  - "max" (float): max temperature. (None if invalid inputs - see logs).
1652
1658
  """
1653
- mth = "osut.scheduleCompactMinMax"
1659
+ mth = "osut.scheduleIntervalMinMax"
1654
1660
  cl = openstudio.model.ScheduleInterval
1655
1661
  vals = []
1656
1662
  res = dict(min=None, max=None)
@@ -1658,10 +1664,19 @@ def scheduleIntervalMinMax(sched=None) -> dict:
1658
1664
  if not isinstance(sched, cl):
1659
1665
  return oslg.mismatch("sched", sched, cl, mth, CN.DBG, res)
1660
1666
 
1661
- vals = sched.timeSeries().values()
1667
+ values = sched.timeSeries().values()
1662
1668
 
1663
- res["min"] = min(values)
1664
- res["max"] = max(values)
1669
+ for i in range(len(values)):
1670
+ try:
1671
+ value = float(values[i])
1672
+ vals.append(value)
1673
+ except:
1674
+ oslg.invalid("numerical at %d" % i, mth, 1, CN.ERR)
1675
+
1676
+ if not vals: return res
1677
+
1678
+ res["min"] = min(vals)
1679
+ res["max"] = max(vals)
1665
1680
 
1666
1681
  try:
1667
1682
  res["min"] = float(res["min"])
@@ -2595,6 +2610,17 @@ def availabilitySchedule(model=None, avl=""):
2595
2610
 
2596
2611
  return schedule
2597
2612
 
2613
+ # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- #
2614
+ # This final set of utilities targets OpenStudio geometry. Many of the
2615
+ # following geometry methods rely on Boost as an OpenStudio dependency.
2616
+ # As per Boost requirements, points (e.g. vertical polygon) must be 'aligned':
2617
+ # - first rotated/tilted as to lay flat along XY plane (Z-axis ~= 0)
2618
+ # - initial Z-axis values now become Y-axis values
2619
+ # - points with the lowest X-axis values are 'aligned' along X-axis (0)
2620
+ # - points with the lowest Z-axis values are 'aligned' along Y-axis (0)
2621
+ # - for several Boost methods, points must be clockwise in sequence
2622
+ #
2623
+ # Check OSut's poly() method, which offers such Boost-related options.
2598
2624
 
2599
2625
  def transforms(group=None) -> dict:
2600
2626
  """"Returns OpenStudio site/space transformation & rotation angle.
@@ -2698,7 +2724,7 @@ def p3Dv(pts=None) -> openstudio.Point3dVector:
2698
2724
  pts (list): OpenStudio 3D points.
2699
2725
 
2700
2726
  Returns:
2701
- openstudio.Point3dVector: Vector of 3D points (see logs if empty).
2727
+ openstudio.Point3dVector: Vector of 3D points (see 'p3Dv' logs if empty).
2702
2728
 
2703
2729
  """
2704
2730
  mth = "osut.p3Dv"
@@ -2711,7 +2737,10 @@ def p3Dv(pts=None) -> openstudio.Point3dVector:
2711
2737
  elif isinstance(pts, openstudio.Point3dVector):
2712
2738
  return pts
2713
2739
  elif isinstance(pts, openstudio.model.PlanarSurface):
2714
- pts = list(pts.vertices())
2740
+ for vt in pts.vertices():
2741
+ pt = openstudio.Point3d(vt.x(), vt.y(), vt.z())
2742
+ v.append(pt)
2743
+ return v
2715
2744
 
2716
2745
  try:
2717
2746
  pts = list(pts)
@@ -3030,7 +3059,7 @@ def nextUp(pts=None, pt=None):
3030
3059
  None: If invalid inputs (see logs).
3031
3060
 
3032
3061
  """
3033
- mth = "osut.nextUP"
3062
+ mth = "osut.nextUp"
3034
3063
  pts = p3Dv(pts)
3035
3064
  cl = openstudio.Point3d
3036
3065
 
@@ -3168,7 +3197,7 @@ def uniques(pts=None, n=0) -> openstudio.Point3dVector:
3168
3197
  Requested number of unique points (0 returns all).
3169
3198
 
3170
3199
  Returns:
3171
- openstudio.Point3dVector: Unique points (see logs if empty).
3200
+ openstudio.Point3dVector: Unique points (see logs).
3172
3201
 
3173
3202
  """
3174
3203
  mth = "osut.uniques"
@@ -3179,7 +3208,8 @@ def uniques(pts=None, n=0) -> openstudio.Point3dVector:
3179
3208
  try:
3180
3209
  n = int(n)
3181
3210
  except:
3182
- return oslg.mismatch("n unique points", n, int, mth, CN.DBG, v)
3211
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3212
+ n = 0
3183
3213
 
3184
3214
  for pt in pts:
3185
3215
  if not holds(v, pt): v.append(pt)
@@ -3314,7 +3344,7 @@ def isPointAlongSegment(p0=None, sg=[]) -> bool:
3314
3344
 
3315
3345
  Returns:
3316
3346
  bool: Whether a 3D point lies ~along a 3D point segment.
3317
- False: If invalid inputs.
3347
+ False: If invalid inputs (see logs).
3318
3348
 
3319
3349
  """
3320
3350
  mth = "osut.isPointAlongSegment"
@@ -3323,12 +3353,10 @@ def isPointAlongSegment(p0=None, sg=[]) -> bool:
3323
3353
 
3324
3354
  if not isinstance(p0, cl1):
3325
3355
  return oslg.mismatch("point", p0, cl1, mth, CN.DBG, False)
3326
- if not isSegment(sg):
3327
- return oslg.mismatch("segment", sg, cl2, mth, CN.DBG, False)
3328
-
3356
+ if not isSegment(sg): return False
3329
3357
  if holds(sg, p0): return True
3330
3358
 
3331
- a = sg[0]
3359
+ a = sg[ 0]
3332
3360
  b = sg[-1]
3333
3361
  ab = b - a
3334
3362
  abn = b - a
@@ -3370,7 +3398,7 @@ def isPointAlongSegments(p0=None, sgs=[]) -> bool:
3370
3398
  if not sgs:
3371
3399
  return oslg.empty("segments", mth, CN.DBG, False)
3372
3400
  if not isinstance(p0, cl1):
3373
- return oslg.mismatch("point", p0, cl, mth, CN.DBG, False)
3401
+ return oslg.mismatch("point", p0, cl1, mth, CN.DBG, False)
3374
3402
 
3375
3403
  for sg in sgs:
3376
3404
  if isPointAlongSegment(p0, sg): return True
@@ -3444,14 +3472,6 @@ def lineIntersection(s1=[], s2=[]):
3444
3472
  xa1b1 = a.cross(a1b1)
3445
3473
  xa1b2 = a.cross(a1b2)
3446
3474
 
3447
- if xa1b1.length() < CN.TOL2:
3448
- if isPointAlongSegment(a1, [a2, b1]): return None
3449
- if isPointAlongSegment(a2, [a1, b1]): return None
3450
-
3451
- if xa1b2.length() < CN.TOL2:
3452
- if isPointAlongSegment(a1, [a2, b2]): return None
3453
- if isPointAlongSegment(a2, [a1, b2]): return None
3454
-
3455
3475
  # Both segment endpoints can't be 'behind' point.
3456
3476
  if a.dot(a1b1) < 0 and a.dot(a1b2) < 0: return None
3457
3477
 
@@ -3632,7 +3652,7 @@ def blc(pts=None) -> openstudio.Point3dVector:
3632
3652
 
3633
3653
 
3634
3654
  def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3635
- """Returns sequential non-collinear points in an OpenStudio 3D point vector.
3655
+ """Returns non-collinear points in an OpenStudio 3D point vector.
3636
3656
 
3637
3657
  Args:
3638
3658
  pts (openstudio.Point3dVector):
@@ -3641,11 +3661,10 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3641
3661
  Requested number of non-collinears (0 returns all).
3642
3662
 
3643
3663
  Returns:
3644
- openstudio.Point3dVector: non-collinears (see logs if empty).
3664
+ openstudio.Point3dVector: non-collinears (see logs).
3645
3665
 
3646
3666
  """
3647
3667
  mth = "osut.nonCollinears"
3648
- v = openstudio.Point3dVector()
3649
3668
  a = []
3650
3669
  pts = uniques(pts)
3651
3670
  if len(pts) < 3: return pts
@@ -3653,12 +3672,8 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3653
3672
  try:
3654
3673
  n = int(n)
3655
3674
  except:
3656
- oslg.mismatch("n non-collinears", n, int, mth, CN.DBG, v)
3657
-
3658
- if n > len(pts):
3659
- return oslg.invalid("+n non-collinears", mth, 0, CN.ERR, v)
3660
- elif n < 0 and abs(n) > len(pts):
3661
- return oslg.invalid("-n non-collinears", mth, 0, CN.ERR, v)
3675
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3676
+ n = 0
3662
3677
 
3663
3678
  # Evaluate cross product of vectors of 3x sequential points.
3664
3679
  for i2, p2 in enumerate(pts):
@@ -3680,9 +3695,7 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3680
3695
  a.rotate(1)
3681
3696
  a = list(a)
3682
3697
 
3683
- if n > len(a): return p3Dv(a)
3684
- if n < 0 and abs(n) > len(a): return p3Dv(a)
3685
-
3698
+ if abs(n) > len(a): n = 0
3686
3699
  if n > 0: a = a[0:n]
3687
3700
  if n < 0: a = a[n:]
3688
3701
 
@@ -3691,7 +3704,7 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3691
3704
 
3692
3705
  def collinears(pts=None, n=0) -> openstudio.Point3dVector:
3693
3706
  """
3694
- Returns sequential collinear points in an OpenStudio 3D point vector.
3707
+ Returns collinear points in an OpenStudio 3D point vector.
3695
3708
 
3696
3709
  Args:
3697
3710
  pts (openstudio.Point3dVector):
@@ -3700,38 +3713,31 @@ def collinears(pts=None, n=0) -> openstudio.Point3dVector:
3700
3713
  Requested number of collinears (0 returns all).
3701
3714
 
3702
3715
  Returns:
3703
- openstudio.Point3dVector: collinears (see logs if empty).
3716
+ openstudio.Point3dVector: collinears (see logs).
3704
3717
 
3705
3718
  """
3706
3719
  mth = "osut.collinears"
3707
- v = openstudio.Point3dVector()
3708
- a = []
3720
+ a = openstudio.Point3dVector()
3709
3721
  pts = uniques(pts)
3710
3722
  if len(pts) < 3: return pts
3711
3723
 
3712
3724
  try:
3713
3725
  n = int(n)
3714
3726
  except:
3715
- oslg.mismatch("n collinears", n, int, mth, CN.DBG, v)
3716
-
3717
- if n > len(pts):
3718
- return oslg.invalid("+n collinears", mth, 0, CN.ERR, v)
3719
- elif n < 0 and abs(n) > len(pts):
3720
- return oslg.invalid("-n collinears", mth, 0, CN.ERR, v)
3727
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3728
+ n = 0
3721
3729
 
3722
3730
  ncolls = nonCollinears(pts)
3723
- if not ncolls: return pts
3731
+ if not ncolls: return a
3724
3732
 
3725
3733
  for pt in pts:
3726
3734
  if pt not in ncolls: a.append(pt)
3727
3735
 
3728
- if n > len(a): return p3Dv(a)
3729
- if n < 0 and abs(n) > len(a): return p3Dv(a)
3730
-
3736
+ if abs(n) > len(a): n = 0
3731
3737
  if n > 0: a = a[0:n]
3732
3738
  if n < 0: a = a[n:]
3733
3739
 
3734
- return p3Dv(a)
3740
+ return a
3735
3741
 
3736
3742
 
3737
3743
  def poly(pts=None, vx=False, uq=False, co=False, tt=False, sq="no") -> openstudio.Point3dVector:
@@ -5237,15 +5243,12 @@ def spaceHeight(space=None) -> float:
5237
5243
  (float): Full height of space (0.0 if invalid input).
5238
5244
 
5239
5245
  """
5240
- if not isinstance(space, openstudio.model.Space):
5241
- return 0
5246
+ hght = 0
5247
+ if not isinstance(space, openstudio.model.Space): return 0
5242
5248
 
5243
- hght = 0
5244
5249
  minZ = 10000
5245
5250
  maxZ = -10000
5246
5251
 
5247
- # The solution considers all surface types: "Floor", "Wall", "RoofCeiling".
5248
- # No presumption that floor are necessarily at ground level.
5249
5252
  for surface in space.surfaces():
5250
5253
  zs = [pt.z() for pt in surface.vertices()]
5251
5254
  minZ = min(minZ, min(zs))
@@ -5288,21 +5291,18 @@ def spaceWidth(space=None) -> float:
5288
5291
  # - retain only other floor surfaces sharing same 3D plane
5289
5292
  # - recover potential union between floor surfaces
5290
5293
  # - fall back to largest floor surface if invalid union
5294
+ # - return width of largest bounded box
5291
5295
  floors = sorted(floors, key=lambda fl: fl.grossArea(), reverse=True)
5292
5296
  floor = floors[0]
5293
5297
  plane = floor.plane()
5294
5298
  t = openstudio.Transformation.alignFace(floor.vertices())
5295
5299
  polyg = list(poly(floor, False, True, True, t, "ulc"))
5296
-
5297
- if not polyg:
5298
- oslg.clean()
5299
- return 0
5300
+ if not polyg: return 0
5300
5301
 
5301
5302
  polyg.reverse()
5302
- polyg = p3Dv(polyg)
5303
5303
 
5304
5304
  if len(floors) > 1:
5305
- floors = [flr for flr in floors if plane.equal(fl.plane(), 0.001)]
5305
+ floors = [flr for flr in floors if plane.equal(flr.plane(), 0.001)]
5306
5306
 
5307
5307
  if len(floors) > 1:
5308
5308
  polygs = [poly(flr, False, True, True, t, "ulc") for flr in floors]
@@ -5315,12 +5315,16 @@ def spaceWidth(space=None) -> float:
5315
5315
 
5316
5316
  union = openstudio.joinAll(polygs, 0.01)[0]
5317
5317
  polyg = poly(union, False, True, True)
5318
+ if not polyg: return 0
5319
+
5320
+ polyg = list(polyg)
5321
+ polyg.reverse()
5318
5322
 
5319
- box = boundedBox(polyg)
5320
- oslg.clean()
5323
+ res = realignedFace(polyg)
5324
+ if not res["box"]: return 0
5321
5325
 
5322
5326
  # A bounded box's 'height', at its narrowest, is its 'width'.
5323
- return height(box)
5327
+ return height(res["box"])
5324
5328
 
5325
5329
 
5326
5330
  def genAnchors(s=None, sset=[], tag="box") -> int:
@@ -6079,7 +6083,7 @@ def genSlab(pltz=[], z=0) -> openstudio.Point3dVector:
6079
6083
  slb = vtx
6080
6084
 
6081
6085
  # Once joined, re-adjust Z-axis coordinates.
6082
- if abs(z) > CN.TOL:
6086
+ if round(z, 2) != 0.00:
6083
6087
  vtx = openstudio.Point3dVector()
6084
6088
 
6085
6089
  for pt in slb: vtx.append(openstudio.Point3d(pt.x(), pt.y(), z))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osut
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: OpenStudio SDK utilities for Python
5
5
  Author-email: Denis Bourgeois <denis@rd2.ca>
6
6
  Maintainer-email: Denis Bourgeois <denis@rd2.ca>
@@ -16,7 +16,6 @@ Requires-Python: >=3.2
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: oslg
19
- Requires-Dist: openstudio>=3.6.1
20
19
  Dynamic: license-file
21
20
 
22
21
  # pyOSut
@@ -25,3 +24,27 @@ Python implementation of the _OSut_ Ruby gem for the OpenStudio SDK.
25
24
  - PyPi [package](https://pypi.org/project/osut/)
26
25
  - Ruby [gem](https://rubygems.org/gems/osut)
27
26
  - Ruby GitHub [repository](https://github.com/rd2/osut)
27
+
28
+ ----
29
+
30
+ _OSut_ interacts with _OpenStudio_:
31
+
32
+ `pip install openstudio`
33
+
34
+ ----
35
+
36
+ To download the _OSut_ Python package:
37
+
38
+ `pip install --upgrade osut`
39
+
40
+ ----
41
+
42
+ To import the _OSut_ module in a Python project:
43
+
44
+ `from osut import osut`
45
+
46
+ ____
47
+
48
+ To run the _OSut_ unit tests on a `git clone` of the repo:
49
+
50
+ `python -m unittest`
@@ -0,0 +1,7 @@
1
+ osut/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ osut/osut.py,sha256=YyaK3ft1WRnPrAGkNGeTCA48ya_TapHccQkAmLm20M4,302755
3
+ osut-0.7.0.dist-info/licenses/LICENSE,sha256=Ag_zDZp4XtiEQWfCwuPk25nVa9gsNhJ3SIx2Ahh1I6c,1495
4
+ osut-0.7.0.dist-info/METADATA,sha256=6l48aPzuzXNVJXckxyvtEuq5Sl7g2M5GSAT2CSm8kzA,1254
5
+ osut-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ osut-0.7.0.dist-info/top_level.txt,sha256=elxZoPwvGd11mNFvZvnG07mjsDiiiiU2VwmzXjnSWT4,5
7
+ osut-0.7.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- osut/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- osut/osut.py,sha256=wFIU7ZhAVK5b7i0pYFBxmwwDskGIdNrdRMY2RYrPyM8,302342
3
- osut-0.6.0.dist-info/licenses/LICENSE,sha256=Ag_zDZp4XtiEQWfCwuPk25nVa9gsNhJ3SIx2Ahh1I6c,1495
4
- osut-0.6.0.dist-info/METADATA,sha256=iQJMXVJfPi_yDD_kx-Uo4RPfSm-U0TkPeAOdQR9umxc,972
5
- osut-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- osut-0.6.0.dist-info/top_level.txt,sha256=elxZoPwvGd11mNFvZvnG07mjsDiiiiU2VwmzXjnSWT4,5
7
- osut-0.6.0.dist-info/RECORD,,
File without changes