tv_frame/cover.svg
tv_frame
Adam Vermeer

Building a TV Frame

If you're not keen on reading through my design work, that's a bit of a shame, but I understand; we're busy people. Feel free to check out the downloads at the bottom of the article, though!

Several years ago I built a weird looking desk that had a PC and TV built right in. It never progressed beyond 'usable prototype', and was eventually disassembled. The TV had been partially stripped down to fit nicely in the desk's surface, and the old parts were discarded.

Since the TV is still good, I want to design a frame and stand that will allow me to actually use it again.

Requirements

The frame must meet the following criteria:

Details and Images

Before the design can properly begin, some measurements will be needed. I walked around the shop and discovered a half-used bundle of some interesting material.

After checking that it was actually OK to use a length or two of this (it was), I pulled one length out and measured. The dimensions are shown below.

Conveniently, there is a work in progress dxf importer for CadQuery that should make it much simpler to work with 2D objects.

So, I made a .dxf file very similar to the above SVG, and imported it to create the tube that I'll use to define all of the tv frame's parts.

# make a simple sample piece 
# to try out the dxf import function
sample_tube = (cq.Workplane('XY')
               .workplane(offset=-1)
               .dxf('downloads/profile.dxf')
               .extrude(2)
              )

show_object(sample_tube)

OPEN SCENE IN NEW TAB

Alright, it works! Now I can work on the actual design.

Parts Plan

This design is going to be a bit more advanced than my ipad_stand, so I've got a mental picture in mind that will take a few different parts:

Naming things is, generally speaking, a difficult thing to do. As such, it's much more sensible to see the parts than it is to rely solely on names. The next steps will be defining the CadQuery code to generate all of the parts in the list.

Outer Frame Horizontals

The horizontal frame pieces are identical, with a small exception for the bottom bar: two holes are cut in order to provide a mounting location for the legs.

Outer Horizontal Top

L = 31.6772 # comes from width of scrn + offset due to tube dims
tap_01875 = 0.1495

outer_h_top = (cq.Workplane('XY')
               .workplane(offset=-L/2.0)
               .dxf('downloads/profile.dxf')
               .extrude(L)
               # holes on top face near edge
               # will be tapped with 3/16 bolt thread
               # used to mount inner frame tube
               .faces('>Y').workplane()
               .pushPoints([(-0.5,  13.3386),
                            (-0.5, -13.3386),
                            (-0.5,  6.6693),
                            (-0.5, -6.6693),
                            (-0.5, 0.0),])
               .hole(tap_01875, depth=0.08)
               # holes centered on back face
               # 2 pairs of 3/16 tapped holes
               # will mount back 'lock bar' tubes
               .faces('<X').workplane()
               .pushPoints([(0.0, 6.8386),
                            (0.0, 7.8386),
                            (0.0, -6.8386),
                            (0.0, -7.8386)])
               .hole(tap_01875, depth=0.08)
               # Cut the tube ends to create a 'dovetail'
               # joint to hold the vertical tubes in place
               # back left
               .faces('>Y').workplane()
               .moveTo(0.425, 15.3386)
               .polyline([(0.425, 15.4086),
                         (0.5, 15.839),
                         (0.75, 15.839),
                         (0.75, 15.3386)]).close()
               .cutThruAll()
               # front left
               .faces('>Y').workplane(centerOption='CenterOfBoundBox')
               .moveTo(-0.425, 15.3386)
               .polyline([(-0.425, 15.4086),
                         (-0.5, 15.839),
                         (-0.75, 15.839),
                         (-0.75, 15.3386)]).close()
               .cutThruAll()
               # back right
               .faces('>Y').workplane(centerOption='CenterOfBoundBox')
               .moveTo(0.425, -15.3386)
               .polyline([(0.425, -15.4086),
                         (0.5, -15.839),
                         (0.75, -15.839),
                         (0.75, -15.3386)]).close()
               .cutThruAll()
               # front right
               .faces('>Y').workplane(centerOption='CenterOfBoundBox')
               .moveTo(-0.425, -15.3386)
               .polyline([(-0.425, -15.4086),
                         (-0.5, -15.839),
                         (-0.75, -15.839),
                         (-0.75, -15.3386)]).close()
               .cutThruAll()
              )

show_object(outer_h_top)

OPEN SCENE IN NEW TAB

Outer Horizontal Bottom

tap_025 = 0.201

outer_h_bottom = (outer_h_top
                  .faces('<Y').workplane(centerOption='CenterOfBoundBox')
                  .pushPoints([(0.0,  7.3386),
                               (0.0, -7.3386)])
                  .hole(tap_01875, depth=0.08)
                 )

show_object(outer_h_bottom)

OPEN SCENE IN NEW TAB

Outer Verticals

L = 19.6693 # comes from height of scrn + offset due to tube dims
tap_01875 = 0.1495

outer_v = (cq.Workplane('XY')
               .workplane(offset=-L/2.0)
               .dxf('downloads/profile.dxf')
               .extrude(L)
               # holes on top face near edge
               # will be tapped with 3/16 bolt thread
               # used to mount inner frame tube
               .faces('<Y').workplane()
               .pushPoints([(-0.5,  8.8507),
                            (-0.5, -8.8507),
                            (-0.5,  4.4173),
                            (-0.5, -4.4173),
                            (-0.5, 0.0),])
               .hole(tap_01875, depth=0.08)
               # Cut the tube ends to create the
               # 'dovetail' slot
               # left
               .faces('>Z').workplane(centerOption='CenterOfBoundBox')
               .moveTo(0.0, -0.135).rect(0.85, 0.25, centered=True)
               .cutBlind(-0.5, clean=True)
               .faces('>Z').workplane(centerOption='CenterOfBoundBox')
               .moveTo(0.0, 0.135).rect(1.0, 0.25, centered=True)
               .cutBlind(-0.5, clean=True)
               # right
               .faces('<Z').workplane(centerOption='CenterOfBoundBox')
               .moveTo(0.0, 0.135).rect(0.85, 0.25, centered=True)
               .cutBlind(-0.5, clean=True)
               .faces('<Z').workplane(centerOption='CenterOfBoundBox')
               .moveTo(0.0, -0.135).rect(1.0, 0.25, centered=True)
               .cutBlind(-0.5, clean=True)
              )

show_object(outer_v)

OPEN SCENE IN NEW TAB

Inner Frame Horizontals and Verticals

The inner frame components are identically designed. The horizontal bars are equivalent, as are the vertical bars. The only difference between the verticals and horizontals is the length. As such, the following code is parametric according to L, the tube's total length.

L = 27.6772 # horizontals
# L = 18.6693 # verticals
tap_01875 = 0.1495

pnts = [(0.0, 0.0),
        (0.0,  (L/2.0 - 0.5) ),
        (0.0, -(L/2.0 - 0.5) ),
        (0.0,  (L/2.0 - 0.5)/2.0 ),
        (0.0, -(L/2.0 - 0.5)/2.0 )]

inner = (cq.Workplane('XY')
               .workplane(offset=-L/2.0)
               .dxf('downloads/profile.dxf')
               .extrude(L)
               # holes on the side which are
               # used to mount the inner frame
               # to the outer frame
               .faces('>X').workplane()
               .pushPoints(pnts)
               .hole(tap_01875, depth=0.08)
               # Access holes cut on wide face
               # which will be facing inwards
               # when the whole frame is assembled
               .faces('>Y').workplane()
               .pushPoints(pnts)
               .rect(1.05, 0.5)
               .cutBlind(-0.08)
              )

Inner Horizontals

show_object(inner_h)

OPEN SCENE IN NEW TAB

Inner Verticals

show_object(inner_v)

OPEN SCENE IN NEW TAB

Stands

L = 10.0

stand = (cq.Workplane('XY')
               .workplane(offset=-L/2.0)
               .dxf('downloads/profile.dxf')
               .extrude(L)
               # hole on top for .25in bolt to pass through
               .faces('>Y').workplane()
               .hole(0.257, depth=0.08)
               # Access hole on bottom
               .faces('<Y').workplane()
               .hole(0.9375, depth=0.08)
              )

show_object(stand)

OPEN SCENE IN NEW TAB

Lock Bars

L = 19.6693 # comes from height of scrn + offset due to tube dims
tap_025 = 0.201

pnts = [(0.0, 0.0 ),
        (0.0,  (L/2.0 - 3.0) ),
        (0.0, -(L/2.0 - 3.0) )]

end_holes = [( 0.5,  (L/2.0 - 0.25) ),
             (-0.5,  (L/2.0 - 0.25) ),
             ( 0.5, -(L/2.0 - 0.25) ),
             (-0.5, -(L/2.0 - 0.25) )]

lock_bar = (cq.Workplane('XY')
               .workplane(offset=-L/2.0)
               .dxf('downloads/profile.dxf')
               .extrude(L)
               # hole on bottom for .25in bolt
               # to be threaded through for locking
               .faces('<Y').workplane()
               .pushPoints(pnts)
               .hole(tap_025, depth=0.08)
               # Access hole on top
               .faces('>Y').workplane()
               .pushPoints(pnts)
               .hole(0.9375, depth=0.08)
               # holes on ends for 3/16in 
               # screws to pass through
               .faces('<Y').workplane()
               .pushPoints(end_holes)
               .hole(0.1875, depth=0.08)
               # cut 45deg. triangles from
               # tube ends for screw access
               .faces('>X').workplane(centerOption='CenterOfBoundBox')
               .moveTo(-0.25, (L/2.0) )
               .polyline([( 0.25, (L/2.0) ),
                          ( 0.25, (L/2.0) - 0.5 )]).close()
               .cutThruAll()
               .faces('>X').workplane(centerOption='CenterOfBoundBox')
               .moveTo(-0.25, -(L/2.0) )
               .polyline([( 0.25, -(L/2.0) ),
                          ( 0.25, -(L/2.0) + 0.5 )]).close()
               .cutThruAll()
              )

show_object(lock_bar)

OPEN SCENE IN NEW TAB

Design Assembly

This frame is a bit more complex of an assembly, so the following code might get a bit messy. It'll show off the intended design, though.

import cqjupyter_extras as cqe
import cqtools

# PARTS LIST
# inner_h
# inner_v
# outer_h_bottom
# outer_h_top
# outer_v
# stand
# lock_bar

# all positions are in mm.
# There is definite need to improve the
# assembly workflow here.
asm = [ 
    ['stand', '0 0  186.4', '0 90 0' ],
    ['stand', '0 0 -186.4', '0 90 0' ],
    ['outer_h_bottom', '0 12.7 0', '0 0 0' ],
    ['inner_h', '12.7 38.1 0', '0 0 90' ],
    ['outer_v', '0 256.3 -395.95', '90 90 90' ],
    ['inner_v', '12.7 256.3 -370.55', '270 90 0' ],
    ['outer_v', '0 256.3 395.95', '270 90 270' ],
    ['inner_v', '12.7 256.3 370.55', '90 270 0' ],
    ['outer_h_top', '0 500.0 0', '180 0 0' ],
    ['inner_h', '12.7 474.4 0', '0 0 90' ],
    ['lock_bar', '-25.4 256.3 186.4', '90 270 0' ],
    ['lock_bar', '-25.4 256.3 -186.4', '90 270 0' ],
]

# cqe.cqassemble(asm, name='assembly')
# load the STEP files to build the .STEP assembly file
inner_h = cqtools._loadSTEP('downloads/inner_h.STEP')
inner_v = cqtools._loadSTEP('downloads/inner_v.STEP')
outer_h_bottom = cqtools._loadSTEP('downloads/outer_h_bottom.STEP')
outer_h_top = cqtools._loadSTEP('downloads/outer_h_top.STEP')
outer_v = cqtools._loadSTEP('downloads/outer_v.STEP')
stand = cqtools._loadSTEP('downloads/stand.STEP')
lock_bar = cqtools._loadSTEP('downloads/lock_bar.STEP')

asm2 = [
    (stand
         .rotate((0,0,0), (0,1,0), 90)
         .translate((0, 0, 186.4))
         .findSolid()
    ),
    (stand
         .rotate((0,0,0), (0,1,0), 90)
         .translate((0, 0, -186.4))
         .findSolid()
    ),
    (outer_h_bottom
         .rotate((0,0,0), (1,0,0), 0)
         .translate((0, 12.7, 0))
         .findSolid()
    ),
    (inner_h
         .rotate((0,0,0), (0,0,1), 90)
         .rotate((0,0,0), (1,0,0), 180)
         .translate((12.7, 38.1, 0))
         .findSolid()
    ),
    (outer_v
         .rotate((0,0,0), (1,0,0), 270)
         .rotate((0,0,0), (0,0,1), 180)
         .translate((0, 256.3, -395.95))
         .findSolid()
    ),
    (inner_v
         .rotate((0,0,0), (0,0,1), 90)
         .rotate((0,0,0), (1,0,0), 270)
         .translate((12.7, 256.3, -370.55))
         .findSolid()
    ),
    (outer_v
         .rotate((0,0,0), (1,0,0), 90)
         .rotate((0,0,0), (0,0,1), 180)
         .translate((0, 256.3, 395.95))
         .findSolid()
    ),
    (inner_v
         .rotate((0,0,0), (0,0,1), 90)
         .rotate((0,0,0), (1,0,0), 90)
         .translate((12.7, 256.3, 370.55))
         .findSolid()
    ),
    (outer_h_top
         .rotate((0,0,0), (1,0,0), 180)
         .translate((0, 500.0, 0))
         .findSolid()
    ),
    (inner_h
         .rotate((0,0,0), (0,0,1), 90)
         .translate((12.7, 474.4, 0))
         .findSolid()
    ),
    (lock_bar
         .rotate((0,0,0), (0,0,1), 90)
         .rotate((0,0,0), (1,0,0), 270)
         .translate((-25.4, 256.3, 186.4))
         .findSolid()
    ),
    (lock_bar
         .rotate((0,0,0), (0,0,1), 90)
         .rotate((0,0,0), (1,0,0), 270)
         .translate((-25.4, 256.3, -186.4))
         .findSolid()
    ),
]

# Some weird syntax to properly create the compound geometry
# There is likely a cleaner way to do this, but I'm unaware currently
final = cq.cq.Compound.makeCompound(asm2)
assembly = cq.CQ(final)
show_object(assembly)

Building it for Real

Once again I find myself kicking the laser tube cutter into action. I'm extremely lucky to remember how this thing works. As it turns out, I need to be good at last second maintenance for this thing. I had to search the shop for some hydraulic oil to top up the reservoir! Fortunately, everything still works.

With all the parts cut, it simply comes down to assembly. The pictures below show the process, more or less. Of course, the struggle of tapping the holes and tightening up all of the screws is not pictured.

And, just for fun, here's one final shot of my TV, quietly assisting me in enjoying some Nintendo Switch gaming.

Final Thoughts

I can finally use my TV again. Mission accomplished! I like the industrial look to the whole thing, too; I suspect there's no TV quite like this one.

Yet as always, there is room for improvement.

Process Improvements

Design Improvements

There it is, my design and build of an industrial-strength TV frame. Thanks for reading through my article. If you have any questions or suggestions, I'm glad to listen! Please ask away on my Twitter, @RustyVermeer.

Bits and Pieces

Software

I used several different programs to make this project:
CadQuery Github Page
FreeCAD
Jupyter
CQNB - Jupyter Notebook CadQuery Extension
* A-Frame VR

Downloads

As mentioned at the start of this post, I've got some downloads for you to look at.